From RTSC-Pedia
[printable version] | offline version generated on 02-Oct-2009 22:10 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 RTSCgeneralizing 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 moduleall 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.
|
|
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 XDCspecRTSC's C-like specification languagethe 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 2ignoring the new XDCspec keywordshould 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 modulejust as Talker exists within the scope of the lesson6 package.
As with any programmatic scopesuch as fields within a struct or local variables within a functionall 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 definitionsalbeit 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 modulefound in a source file rather predictably named Talker.cyou'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 thereinin 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 herethe 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 packagebeneath 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.cwhich first searches the current working directoryreinforces 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 bodywhether 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-domainreadable, but not writable. But with a growing number of today's compilers supporting whole program optimizationenabling the compiler to look across multiple source files when generating object codethe 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-domainin 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.useModuleno 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 unfoldsmuch 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 configurationgenerally 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 libraryin 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 callsomething 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.PackageContentsconventionally referenced as Build and Pkg in all of our package.bld scriptsdo 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 modulesthe 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 02-Oct-2009 22:10 UTC |