From RTSC-Pedia

Jump to: navigation, search
revision tip
—— LANDSCAPE orientation
[printable version]  offline version generated on 11-Jun-2009 18:06 UTC  

RTSC Module Primer/Lesson 7

Modules — implementing acme.utils.Bench

We'll turn now to the implementation of the Bench module first seen back in Lesson 2, both to reinforce common programming idioms used when supplying any RTSC module as well as to introduce some additional aspects of the XDCspec language. In particular, we'll use XDCspec not only to identify the public client-visible features of Bench but also to specify certain internal features of Bench used only by the module supplier.

Contents

Specifying the module

Following RTSC conventions, you'll find the spec for the acme.utils.Bench module in a source file named Bench.xdc that in turn resides in a package directory named acme/utils. The latter directory—itself rooted under «examples»—also contains the mandatory package.xdc file that specs the acme.utils package as a whole.

acme/utils/package.xdc
 
 
 
 
/*! Collection of utility modules */
package acme.utils {
    module Bench;
};
 
acme/utils/Bench.xdc
 

 
 
 
2a
 
 
2b
 
 
2c
 

 

 
 
 
 
 
/*! Benchmark timing support */
@ModuleStartup
module Bench {
 
    /*! Enable/disable benchmark functions */
    config Bool enableFlag = true;
 
    /*! Start the benchmark */
    Void begin(String msg);
 
    /*! End the benchmark and print the current time */
    Void end();
 
internal:
 
    struct Module_State {
        String beginMsg;    /* current begin() message */
        Int beginClock;     /* begin() start time */
        Int overhead;       /* timing overhead factor */
    };
}

While package.xdc should look rather familiar to you, Bench.xdc has become much more elaborate than the simple Talker.xdc spec seen in Lesson 6. Still, the config and function declarations at lines 2a through 2c follow the same XDCspec idioms introduced in the earlier Talker example. Here, however, an XDCspec documentation comment of the form /*! ... */ immediately precedes each of these declarations with an appropriate summary description; informally referred to as "XDoc", similar forms of comments appearing at the top of Bench.xdc as well as package.xdc summarize the module and package as a whole.

Most modern programming languages offer direct support for special documentation comments that you can embed within source files—Java offers "JavaDoc", Perl offers "PerlDoc", Ruby offers "RubyDoc", and so forth; integrated tooling then automatically generates appropriate user (client) documentation that includes these comments. By contrast, the C programming language (dating from the 1970s) offers no such inherent support for documentation comments; popular tools like doxygen have traditionally filled this void, enabling suppliers of C-based libraries to suitably annotate header files with a similar style of commentary. With module .xdc files now supplanting .h files in RTSC—and with the latter generated from the former—it only seems natural for XDCspec to offer support for "XDoc". In general, XDCspec documentation comments follow conventions already familiar to users of doxygen which in turn maintains the same look-and-feel of JavaDoc.

Though seemingly at odds with RTSC's general notion of separating specification from implementation, Bench.xdc does declare programmatic features below line 3 that (at least conceptually) should remain hidden from any clients of this module. As a rule, any declarations appearing after the special XDCspec internal keyword—whether constants, types, configs, or functions—will typically support some facet of this module's private implementation. For this reason, clients should absolutely never use any of these internal features directly within their own modules or programs; RTSC module suppliers not only can but will freely change internal implementation details without notice from one version of a package to the next.

In the case of the Bench module, its internal implementation (unlike the Talker module of Lesson 6) uses private variables to retain state information between successive calls to the module's public functions—Bench_begin and Bench_end. For reasons that will become more apparent over time, module suppliers will actually declare these privates variables as individual fields within a well-known internal struct invariably named Module_State. In the example at hand, the definition at line 4 of Bench.xdc introduces three such private variables whose roles will become clear once we examine more of this module's implementation.

Besides updating its internal module state, the target-implementation of Bench (coming momentarily in Bench.c) also needs to perform some special run-time initialization of its state during program startup—before control reaches the main() entry-point of the target-program. By including the special annotation found at line 1 of Bench.xdc—an example of what's termed an XDCspec @attribute—control will automatically enter a well-known target-function implemented by the module supplier.

Implementing the module (target-domain)

The target-domain implementation of acme.utils.Bench found in Bench.c essentially contains the bodies of three functions: Bench_begin and Bench_end, which we've explicitly spec'd in Bench.xdc; and a special target-function named Bench_Module_startup, which we've implicitly spec'd by adding the @ModuleStartup attribute at line 1 of Bench.xdc.

acme/utils/Bench.c
 
 
 
 
1
 
2
 
 
 
 
 
 
 
3
 
 
 
 
 
 
 
4
 
 
 
 
 
 
 
 
 
 
 
 
#include <xdc/runtime/Startup.h>
#include <xdc/runtime/System.h>
#include "package/internal/Bench.xdc.h"
 
#include <time.h>
 
Int Bench_Module_startup(Int state)
{
    Bench_begin(NULL);
    Bench_end();  	/* first-time calibration benchmark */
 
    return Startup_DONE;
}
 
Void Bench_begin(String msg)
{
    if (Bench_enableFlag) {
        module->beginClock = clock();
        module->beginMsg = msg;
    }
}
 
Void Bench_end()
{
    if (Bench_enableFlag) {
        Int elapsed = clock() - module->beginClock; /* elapsed time between now and last begin() */
        if (module->beginMsg == NULL) {             /* first-time calibration call */
            module->overhead = elapsed;             /* retain elapsed time for later correction */
        }
        else {                                      /* correct elapsed time and print result */
            elapsed -= module->overhead;
            System_printf("%s [%d]\n", module->beginMsg, elapsed);
        }
    }
}

For all practical purposes, the Bench_Module_startup function defined at line 2 simply performs a one-time "dummy" benchmark—triggered by passing NULL to Bench_begin—which internally calibrates the overhead of the timing mechanism itself. To indicate startup processing has completed for this particular module, Bench_Module_startup returns the public constant Startup_DONE spec'd by the xdc.runtime.Startup module.

Turning now to the implementation of Bench_begin starting at line 3, we here see the programming idiom used to reference individual state variables—module->varName. In this case, Bench_begin first assigns module->beginClock the result of calling the standard C clock function and then assigns its string-valued argument to module->beginMsg.

Though not necessarily efficient enough or accurate enough for many embedded applications, our use of clock within the body of Bench_begin should remind us that target-implementations of RTSC modules ultimately operate in the world of C; and that these implementations can still leverage existing C-based libraries whenever appropriate. No different than any other piece of client C code using clock, line 1 of Bench.c includes the standard <time.h> header furnished by the underlying compiler.

Using the same module->varName idiom, the function Bench_end at line 4 accesses the values assigned earlier to beginClock and beginMsg when calculating elapsed time and printing the final result; here too, we rely on the standard clock function to obtain the current time. Note also that the variable module->overhead—representing the amount of time consumed by the timing mechanism itself—receives its value during the startup call to Bench_end occuring after line 2. Needless to say, all of this processing depends upon the module config Bench_enableFlag testing true at runtime; otherwise, the functions Bench_begin and Bench_end effectively become no-ops.

Once again, whole program optimization effectively turns the if tests after lines 3 and 4 into equivalent #if preprocessor directives—here, including/excluding all code from the bodies of Bench_begin and Bench_end conditional upon the value of Bench_enableFlag; either way, no test of Bench_enableFlag need occur at runtime, since the optimizer "knows" the value of this extern const when generating object code. Regarding the module->varName idiom—which on the surface appears somewhat heavyweight—rest assured that manipulating module state costs no more than accessing a static variable in C.

Implementing the module (meta-domain)

Remembering that module suppliers will generally implement in two programming domains, we turn now to the meta-domain implementation of the Bench module found in the source file Bench.xs which comprises definitions of several special XDCscript functions.

acme/utils/Bench.xs
5
 
 
 
 
 
6
 
 
 
 
 
function module$use()
{
    xdc.useModule('xdc.runtime.Startup');
    xdc.useModule('xdc.runtime.System');
}
 
function module$static$init(obj)
{
    obj.beginMsg = null;
    obj.beginClock = 0;
    obj.overhead = 0;
}

As in the previous lesson, the special meta-function module$use defined at line 5 identifies all other modules used directly in the target-implementation of Bench and mirrors the initial pair of #include directives seen at the top of Bench.c. Because Bench also relies on internal state variables within its target-implementation, the module's meta-implementation features another special meta-function—module$static$init, used to initialize each of the module's state variables.

The special module$static$init function defined here at line 6 takes as its first argument an XDCscript object whose properties correspond one-to-one with the Module_State fields declared back at line 4 of Bench.xdc. Values assigned to these properties in the meta-domain automatically become statically-initialized fields of a corresponding Module_State variable in the target-domain—similar to how the statically-initialized Bench_enableFlag constant would reflect the last value assigned to the Bench.enableFlag config.

While the initialization performed here is clearly trivial, module$static$init in general can execute arbitrarily complex logic to arrive at a set of initial values for its state variables—such as computing algorithmic tables or building pointer-linked data structures. Remember, the XDCscript meta-language is a full programming language hosted in a rich environment with virtually unlimited resources. Any initialization logic performed in the host-based meta-domain would potentially offset equivalent C code no longer needed in the target-domain—an important optimization that can reduce code-size while accelerating program startup.

At the same time, the target-domain processing performed within the special Bench_Module_startup function defined at line 2 of Bench.c cannot migrate upstream to the meta-domain; put another way, the presence of @ModuleStartup at line 1 of Bench.xdc implies that this particular module demands supplementary initialization not possible within module$static$init. In practice, most modules do not require @ModuleStartup; on the other hand, those modules that do interact directly with underlying peripherals (device drivers, timer managers, and so forth) would require a call during program startup to initialize hardware.

Building the package

Unlike the package examined in Lesson 6, the acme.utils package does not contain any executable programs that use the Bench module—only the sources to Bench itself; Lesson 2 already examined a sample client program that uses Bench. This package consequently features an even more abbreviated build script, one that just calls addLibrary for each active target.

acme/utils/package.bld
 
 
 
 
 
 
var Build = xdc.useModule('xdc.bld.BuildEnvironment');
var Pkg = xdc.useModule('xdc.bld.PackageContents');
 
for each (var targ in Build.targets) {
    Pkg.addLibrary("lib/" + Pkg.name, targ).addObjects(["Bench.c"]);
}

As ever, you can clean and rebuild the package by invoking xdc clean followed by xdc all from the command-line. Output from the latter command (edited slightly to improve readability) traces all the steps involved in turning the sources of spec'd RTSC modules into libraries of compiled object code.

 
 
1
 
 
2
 
 
 
 
 
 
 
3
 
>> xdc all
making package.mak ...
 
generating interfaces for package acme.utils ...
    translating Bench
 
cl64P package/package_acme.utils.c ...
cl64P Bench.c ...
archiving into lib/acme.utils.a64P ...
 
cl86GW package/package_acme.utils.c ...
cl86GW Bench.c ...
archiving into lib/acme.utils.a86GW ...
 
all files complete.

As it turns out, the xdc all command internally builds a standard sequence of sub-goals that you can also invoke directly from the command-line:

  • the command xdc .make would run through line 1 and generates an underlying makefile named package.mak after running the current package.bld script;

  • the command xdc .interfaces would run through line 2 and generates (among other files) both the public and internal header for each spec'd module in the current package;

  • the command xdc .libraries would run through line 3 and, for all targets, compiles each module's target-implementation source file(s)—along with some generated C code associated with the package as whole—and then archives the corresponding object files into the appropriate library(s).

As you might imagine, invoking the xdc .libraries command with a trailing suffix—either ,64P or ,86GW in our current environment—would compile and archive only for the designated target.

See also

Using xdc.runtime Startup How applications boot, run, and shutdown
 
C - Module_startup Module's startup initialization function
Command - xdc eXpanDed C package build command
XDCscript - Module-Body.module$static$init Initialize module's State object
XDCspec - @ModuleStartup This module has a startup initialization function
 
xdc.runtime.Startup.DONE Client documentation for xdc.runtime.Startup.DONE

[printable version]  offline version generated on 11-Jun-2009 18:06 UTC  
Copyright © 2008 The Eclipse Foundation. All Rights Reserved
Views
Personal tools
package reference