From RTSC-Pedia

Jump to: navigation, search
revision tip
—— LANDSCAPE orientation
[printable version]  offline version generated on 04-Aug-2010 21:08 UTC  

RTSC Module Primer/Lesson 6

Modules — generalizing "Hello World"

Before turning to the implementation of acme.utils.Bench and bravo.math.RandGen in the pair of lessons that follow, we'll first dissect a very simple module named Talker that illustrates some common programming idioms used when supplying any RTSC module. The Talker module also illustrates an important design technique seen throughout RTSC—generalizing functionality by replacing "hard-coded" C constants with module configuration parameters.

Contents

Introducing the module

Our Talker module resides in a package named lesson6, found in a corresponding directory named «examples»/lesson6; and like all RTSC packages, the lesson6 directory contains a special package.xdc source file which not only names the package as a whole but now introduces some modules within its programmatic scope.

lesson6/package.xdc
 
 
 
package lesson6 {
    module Talker;
};

Note that package.xdc simply declares the existence of lesson6.Talker as a RTSC module—all client-visible features of the module itself are formally specified in a separate file named Talker.xdc, which we'll examine momentarily. For now, though, consider a sample client program that uses the Talker module.

lesson6/prog.cfg
 
 
1
2
var Talker = xdc.useModule('lesson6.Talker');
 
Talker.text = "Goodnight Moon";
Talker.count = 3;
lesson6/prog.c
 
 
 
 
3
 
 
#include <lesson6/Talker.h>
 
Int main()
{
    Talker_print();
    return 0;
}

Working backwards, calling the function Talker_print at line 3 of the target-program prog.c will repetitively output a client-configurable text-string assigned at line 1 of the corresponding meta-program; the assignment at line 2 controls the repetition count. The Talker module's text and count configs have the values "Hello World" and 1 as their respective defaults, implying that Talker_print will still output something meaningful even with lines 1 and 2 removed from prog.cfg.

Specifying the module

As mentioned earlier, the file Talker.xdc formally specifies the client-visible features of the Talker module illustrated informally by our sample program. Also written in XDCspec—RTSC's C-like specification language—the contents of this file should remind you of the sorts of declarations normally found in C headers included by client programs. Indeed, the xdc command just introduced in Lesson 5 will automatically generate the <lesson6/Talker.h> module header from the Talker.xdc module spec when building the lesson6 package as a whole.

lesson6/Talker.xdc
 
 
1
2
 
3
 
module Talker {
 
    config String text = "Hello World";
    config Int count = 1;
 
    Void print();
}

Again working backwards, the function specification at line 3 mirrors the syntax of standard C, declaring the function's return type (Void in this case) as well as the names and types of its arguments (none in this case); and the config specifications at lines 1 and 2—ignoring the new XDCspec keyword—should remind you of a standard C variable declaration with an optional initializer. Finally, note that the client-visible features text, count, and print ultimately exist within the scope of the Talker module—just as Talker exists within the scope of the lesson6 package.

As with any programmatic scope—such as fields within a struct or local variables within a function—all elements introduced within the scope of a RTSC module must be uniquely named therein; ditto, all modules introduced within the scope of a RTSC package. Enveloping XDCspec definitions of modules and packages alike with braces of the form { ... } parallels familar C syntax for scoped struct and function definitions—albeit at a higher programmatic level.

Implementing the module

Just as client programs in RTSC comprise a target-program (prog.c) as well as a corresponding meta-program (prog.cfg), suppliers of spec'd RTSC modules will generally implement functionality in two complementary programming domains:

  • a target-domain, where C-based functions implemented by the module supplier are eventually bound into client programs executing on specific hardware platforms; and

  • a meta-domain, where XDCscript meta-functions also implemented by the module supplier play an active role during the design-time configuration of these same client programs.

Turning first to the target-domain implementation of the Talker module—found in a source file rather predictably named Talker.c—you'll sense the same "look-and-feel" in its stylized use of C seen in all of the prog.c target-programs we've examined so far.

lesson6/Talker.c
1
2
 
3
 
 
4
5
 
 
#include <xdc/runtime/System.h>
#include "package/internal/Talker.xdc.h"
 
Void Talker_print()
{
    Int i;
    for (i = 0; i < Talker_count; i++) {
        System_printf("%s\n", Talker_text);
    }
}

Like any prog.c client program, a module's target-implementation must first #include the headers of any other RTSC modules used therein—in this case, the Talker module itself is a client of xdc.runtime.System and hence the directive found at line 1. But unlike a top-level RTSC program, a module's target-implementation must then include a special "internal" header file following the pattern illustrated on line 2.

Order is important here—the internal header included on line 2 must appear after all other RTSC modules headers.

As you might imagine, the xdc command generates the internal header (included only by the module's supplier) as well as the public header (included by any of the module's clients) from the same .xdc module spec source file. Note also that the internal module header resides deep inside the current package—beneath a special package sub-directory created and used by the xdc command to hold a wealth of generated files; the distinct #include " ... " syntax used on line 2 of Talker.c—which first searches the current working directory—reinforces the locality of this file. By contrast, the generated public header resides in the main directory of the package itself, accessed by external clients using the more common #include < ... > syntax which in turn relies on -I compiler options to establish a directory search path; suffice it to say, the elements of this search path will automatically comprise the contents of your XDCPATH environment variable (currently just «examples») followed a reference to a special «xdcroot»/packages directory within XDCtools itself.

Moving on to the definition of the Talker_print function beginning at line 3, we once again see the familiar naming idiom ModuleName_elementName used within its body—whether referencing the count and text configs belonging to the Talker module or else the printf function belonging to the xdc.runtime.System module. Needless to say, the values held by Talker_count and Talker_text at lines 4 and 5 reflect earlier assignments to Talker.count and Talker.text at lines 1 and 2 of the client's prog.cfg meta-program; or, if no such assignments had occurred, these configs would instead hold default values spec'd in Talker.xdc.

Despite appearances, you cannot assign values to either Talker_count or Talker_text at runtime as if they were some sort of global variable: while module configs behave like assignable struct fields in the meta-domain, each configuration parameter effectively becomes a C extern const in the target-domain—readable, but not writable. But with a growing number of today's compilers supporting whole program optimization—enabling the compiler to look across multiple source files when generating object code—the seemingly expensive (extern) reference to Talker_count at line 4 would effectively "melt away" as if the value of this config were a #define constant local to Talker.c; and the reference to Talker_text at line 5 would likewise incur no more run-time cost than a string literal appearing at this point in the code. Generalizing from our example, this suggests that RTSC module config declarations can effectively replace #define symbols with a higher-level programming construct that is more flexible, more expressive, more robust, and yet equally efficient.

Finally, let's turn to the implementation of the Talker module within the meta-domain—in a source file named Talker.xs that contains definitions of special XDCscript functions that support inclusion of this module within some client program.

lesson6/Talker.xs
 
 
6
 
function module$use()
{
    xdc.useModule('xdc.runtime.System');
}

Remembering that calls to xdc.useModule in the meta-domain will mirror corresponding #include directives in the target-domain, the call at line 6 of Talker.xs parallels line 1 of Talker.c. As a rule, any module that itself becomes a client of another RTSC module must implement its own version of the special XDCscript function module$use with the necessary calls to xdc.useModule—no different than what we've already seen in prog.cfg meta-programs.

Once the client's prog.cfg meta-program completes, control passes to the special module$use function associated with each RTSC module already designated in prior calls to xdc.useModule; the process then continues recursively until all modules used directly or indirectly by the top-level program have had their opportunity to participate. Needless to say, calling xdc.useModule multiple times on the same module will have no adverse effects as this process unfolds—much as target-domain header files guard against multiple inclusion to ensure the compiler doesn't process the same set of definitions more than once.

We'll introduce several special XDCscript functions besides module$use in the lessons that follow, all of which enable a RTSC module to actively participate in some aspect of RTSC program configuration—generally on the "back-end" of the process once the client meta-program (prog.cfg) has completed. One additional XDCscript function worthy of mention is module$validate, which provides module suppliers with an optional hook for flagging any erroneous conditions in the current program configuration; you could imagine a module$validate function in Talker.xs that checks (say) that the value of Talker.count is non-negative or that Talker.text has not been assigned null. As a rule, downstream compiling/linking of the client program will not proceed if even one module$validate function fails: the net result becomes early detection of serious problems that (at best) may yield obscure downstream compiler/linker errors or else (at worst) may trigger run-time behavior with fatal consequences.

Building the package

No different than Lesson 5, you can once again use various forms of the xdc command to clean, rebuild, and execute the contents of the current package:

xdc clean remove all generated files from the current package
xdc all  or just  xdc compile/link all executable programs for all targets
xdc all,64P  or  xdc all,86GW compile/link all executable programs for the designated target
xdc test run all executable programs for all targets
xdc test,64P  or  xdc test,86GW run all executable programs for the designated target

But since the lesson6 package also contains the Talker module and its target-implementation in Talker.c, however, we'll need to make one important addition to the lesson6/package.bld script which in turns drives the underlying XDCtools build engine.

lesson6/package.bld
 
 
 
 
1
 
 
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(["Talker.c"]);
    Pkg.addExecutable("prog", targ, targ.platform).addObjects(["prog.c"]);
}

Similar to the addExecutable method introduced in Lesson 5, the addLibrary call on line 1 of package.bld prescribes creation of an object code library of the designated name for each active target encountered in the Build.targets collection; and like addExecutable, the result of this call on line 1 becomes input to an addObjects method which here lists all of the target-implementation source files requiring compilation followed by archiving into this library—in this case, just Talker.c. As you might expect, invoking xdc all from the command-line results in some extra lines of output that reflect the additional build steps implied by the new addLibrary call—something we'll dissect in the next lesson when we introduce even more forms of the xdc command.

Unlike our Talker module, the special modules xdc.bld.BuildEnvironment and xdc.bld.PackageContents—conventionally referenced as Build and Pkg in all of our package.bld scripts—do not maintain implementations in the target-domain; there is no BuildEnvironment.c file, for example. Rather, these modules are spec'd as metaonly in their corresponding .xdc files using a special XDCspec keyword, and hence only carry a meta-implementation in a corresponding .xs file. As it turns out, virtually all of content bundled with the XDCtools product (target and platform recipes, command-line tools, and so forth) are realized through spec'd meta-only modules—the only notable exception being the "ordinary" modules like System found within the xdc.runtime package which maintain implementations in the meta-domain and the target-domain.

See also

Command - xdc eXpanDed C package build command
XDCscript - Module-Body.module$use Use other modules required by this module
 
xdc.bld.PackageContents.addLibrary Client documentation for xdc.bld.PackageContents.addLibrary
xdc.bld.Library.addObjects Client documentation for xdc.bld.Library.addObjects

[printable version]  offline version generated on 04-Aug-2010 21:08 UTC  
Copyright © 2008 The Eclipse Foundation. All Rights Reserved
Views
Personal tools
package reference