From RTSC-Pedia

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

RTSC Interface Primer/Lesson 15

Abstract instances — techniques for creating IGen objects

Without much fanfare, the target-implementation of the FirTester_main function presented in Lesson 14 already created an abstract instance by calling the FirTester_PFir_create function provided through the module's PFir proxy. Not knowing the identity of the IFir module bound to this proxy, this particular create call returns an abstract FirTester_PFir_Handle that references some underlying concrete instance object—a newly-created instance that from the client's perspective only supports the abstract functionality specified in the IFir interface.

When using the IGen interface introduced back in Lesson 11 in a similar manner, however, things become a little more complex. Why, because modules like RandGen and SineGen not only inherit the abstract IGen interface, these modules also extend IGen with additional per-instance configs that help shape individual instances upon creation. This lesson examines a continuum of applications that each work with abstract IGen instances, showcasing a variety of RTSC idioms that all strike a balance between concrete creation and abstract usage of these instance objects.

NOTE:  This lesson leverages some new proxy capabilities slated for XDCtools 3.20.
NOTE:  For now, just treat the programming examples as "read-only".

Contents

Creating instances statically — AppSt

We'll begin with a (main) module canonically-named bravo.math2.genapps.AppSt featuring a proxy that inherits IGen, bound by clients to some suitable module such as RandGen or SineGen in the meta-domain. In addition, AppSt defines a module-wide config that clients can assign a statically-created IGen instance using one of the aforementioned IGen modules.

bravo/math2/genapps/AppSt.xdc
 
 
 
 
 
 
1
 
 
2
 
 
 
 
import stdsorg.math.IGen;
 
/*! IGen app using statically-created instances */
module AppSt {
 
    /*! IGen module used in this app */
    proxy PGen inherits IGen;
 
    /*! IGen instance from the same IGen module */
    config PGen.Handle genInst;
 
    /*! Program entry point */
    Int main(Int argc, Char* argv[]);
 

While you've seen numerous declarations of type Mod_Handle in target-domain .c files, we've yet to declare any elements using the corresponding XDCspec type Mod.Handle inside an .xdc file. In this particular example, Mod does not refer to some module outside of AppSt but rather to PGen—a proxy module defined inside the scope of AppSt. Though somewhat pedantic, we could have expressed this type as AppSt.PGen.Handle; or taking matters to their logical conclusion, as bravo.math2.genapps.AppSt.PGen.Handle. Fortunately, the XDCspec language scoping rules enable us to minimize use of fully-qualified names outside of import statements.

Like earlier client programs sketched out in Lesson 11, the target-implementation of AppSt simply prints successive values returned from AppSt_PGen_next, passing the instance object referenced through the AppSt_genInst config.

bravo/math2/genapps/AppSt.c
 
 
 
 
 
 
 
 
 
 
 
 
3
 
 
 
 
 
#include <xdc/runtime/System.h>
#include "package/internal/AppSt.xdc.h"
 
#define COUNT 10
 
Int AppSt_main(Int argc, Char* argv[])
{
    Int i;
 
    System_printf("statically-created instance\n");
 
    for (i = 0; i < COUNT; i++) {
        System_printf("%d ", AppSt_PGen_next(AppSt_genInst)); 
    }
    System_printf("\n");
 
    return 0;
}

The inner function call at line 3 says it all, referencing both the proxy module and instance object spec'd at lines 1 and 2 respectively. As our earlier note implied, the genInst config—spec'd of type PGen.Handle at line 2—becomes a statically-initialized target-domain constant of type AppSt_PGen_Handle referenced at line 3 as AppSt_genInst.

Client configuration.  Consider the progSt.cfg meta-program, which both binds the AppSt.PGen proxy as well as assigns a statically-created instance to AppSt.genInst. Note that since AppSt already supplies a candidate entry-point for Program.main, clients need not write a corresponding progSt.c target-program.

bravo/math2/genapps/progSt.cfg
 
 
 
 
1
2
 
 
var AppSt = xdc.useModule('bravo.math2.genapps.AppSt');
var Program = xdc.useModule('xdc.cfg.Program');
var SineGen = xdc.useModule('bravo.math2.SineGen');
 
AppSt.PGen = SineGen;
AppSt.genInst = AppSt.PGen.create({range: 127, freq: 16}};
 
Program.main = AppSt.main;

Needless to say, clients of AppSt "know" something that the module per-se does not—the identity of the IGen delegate bound to the PGen proxy on line 1 (SineGen in this case). Given this knowledge, the client meta-program takes full advantage here of per-instance configs specific to the concrete SineGen module (such as freq) when creating and assigning an abstract IGen instance on line 2.

In general, though, just as AppSt.PGen accepts any module inheriting the IGen interface, we can likewise assign an instance object to AppSt.genInst created with any IGen module. But here's the constraint—we must take care to use the same IGen module on the lines 1 and 2 in this example; otherwise, fatal runtime consequences may ensue.

The idiom illustrated here—first bind Mod.Proxy, then call Mod.Proxy.create—ensures some level of consistency when creating abstract instances. Though not necessarily enforced, you should think of Mod.Proxy as a write-once property—one that you should assign before using any other features of Mod.Proxy in the meta-domain, such as its special create function. Since the latter function defers to the proxy's current delegate, Mod.Proxy must of course already be bound; and needless to say, assigning a different module to Mod.Proxy after calling Mod.Proxy.create breaks everything. Fortunately, a module like AppSt can programatically validate that the concrete module used to create the abstract instance held in AppSt.genInst matches the concrete module currently assigned to AppSt.PGen.

Creating instances dynamically — AppDy

Our next (main) module—canonically-named bravo.math2.genapps.AppDy—not only creates abstract IGen instances during execution, it also enables the client to shape certain characteristics of these instances through command-line arguments passed to the program at runtime. Consider the spec of AppDy together with its implementation in the target-domain.

bravo/math2/genapps/AppDy.xdc
 
 
 
 
 
 

 
 
 
 
import stdsorg.math.IGen;
 
/*! IGen app using dynamically-created instances */
module AppDy {
 
    /*! IGen module used in this app */
    proxy PGen inherits IGen;
 
    /*! Program entry point */
    Int main(Int argc, Char* argv[]);
 

bravo/math2/genapps/AppDy.c
 
 
 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 

 
 
 

 
 

 
 
 
 
 
#include <xdc/runtime/System.h>
#include "package/internal/AppDy.xdc.h"
 
#include <stdlib.h>
 
#define COUNT 10
 
Int AppDy_main(Int argc, Char* argv[])
{
    AppDy_PGen_Handle genInst;
    AppDy_PGen_Params genParams;
    Int i;
 
    AppDy_PGen_Params* prmsRef = NULL;         /* passed to create */
 
    System_printf("dynamically-created instance\n");
 
    if (argc > 1) {                          /* assign range param */
        AppDy_PGen_Params_init(&genParams);
        genParams.range = atoi(argv[1]);
        prmsRef = &genParams;                  /* passed to create */
    }
 
    genInst = AppDy_PGen_create(prmsRef, NULL);
 
    for (i = 0; i < COUNT; i++) {
        System_printf("%d ", AppDy_PGen_next(genInst)); 
    }
    System_printf("\n");
 
    return 0;
}

Like the earlier AppSt module, AppDy also specs a proxy named PGen at line 1 which inherits the IGen interface. But unlike the earlier AppSt module—which featured a module-wide config named genInst that held an statically-created abstract IGen instance—AppDy instead declares the identifier genInst as a local variable of type AppDy_PGen_Handle at line 2 of its target-implementation. The code at lines 4 and 5 then proceed to create and use genInst at runtime, all the while employing the AppDy_PGen proxy as an intermediary.

Here again, the target-implementation of AppDy has no prior knowledge of the actual delegate module bound to its PGen proxy—RandGen, SineGen, or any other module that inherits the IGen interface. For this very reason, we can only pass per-instance config params to AppDy_PGen_create on line 4 defined at the level of IGen itself—configs common to any present or future module directly or indirectly inheriting this interface.

In the example at hand, this suggests that we can assign the common range parameter—as we do at line 3, applying the standard C function atoi to the program's first command-line argument. But we cannot (say) assign the freq parameter specific to the SineGen module; indeed, the AppDy_PGen_Params structure wouldn't even have a field of that name.

Note that if AppDy_main receives too few command-line arguments at runtime, the local variable prmsRef remains NULL when passed to the AppDy_PGen_create function at line 4; in this case, we'll just have to accept default values for all per-instance config params.

Client configuration.  Here again, clients of AppDy who bind its PGen proxy in the meta-domain "know" the true identity of the IGen delegate module eventually used in the target-program. And while AppDy supports instance creation only at runtime—and shaped only by config params common to all IGen modules—we can however associate a set of delegate-specific, per-instance configs as part of the overall process of proxy binding.

bravo/math2/genapps/progDy.cfg
 
 
 
 
1
2
 
 
var AppDy = xdc.useModule('bravo.math2.genapps.AppDy');
var Program = xdc.useModule('xdc.cfg.Program');
var SineGen = xdc.useModule('bravo.math2.SineGen');
 
AppDy.PGen = SineGen;
AppDy.PGen.defaultParams$ = new SineGen.Params({range: 127, freq: 16}};
 
Program.main = AppDy.main;

Once again, we've bound SineGen as the delegate for the PGen proxy at line 1. Taking no further actions, all instances created dynamically through the PGen proxy will accept default values for all per-instance configs not specified in the IGen interface—such as the freq param, which defaults to 1.

However, by then assigning the special defaultParams$ property associated with any proxy module supporting instances—as we've done with AppDy.PGen on line 2—we can effectively override defaults for all per-instance config params otherwise supplied by the proxy's delegate. In this case, all abstract IGen instances created dynamically by calling AppDy_PGen_create at line 4 of ApDy.c will use 16 as their value for freq; passing NULL as the first argument here will additionally use 127 as their value for range. As a semantic constraint—this time automatically enforced by XDCscript—the object assigned to our proxy's defaultParams$ property on line 2 must originate from the same module bound as the proxy's delegate on line 1.

Despite appearances, defaultParams$ is not a property of the PGen delegate—SineGen in this case—but rather a property of the XDCscript object representing the PGen proxy itself. As it turns out, reading/writing AppDy.PGen becomes a "short-cut" that in reality accesses the special proxy property AppDy.PGen.delegate$ behind the scenes. If the asymmetry between lines 1 and 2 bothers you, feel free to bind proxies by explicitly assigning to their delegate$ property.

As you can probably imagine, the built-in new operator used on line 2 fabricates a fresh XDCscript object of type SineGen.Params, initializing a subset of its defined properties along the way; like other objects in the meta-domain, this particular one will eventually become a statically-initialized C structure of type SineGen_Params in the target-domain.

Program execution.  As an alternative to some form of xdc test, we'll load and run the sample programs in this lesson using the xdc.tools.loader utility invoked using the standard xs command bundled with XDCtools product—significantly streamlining ad hoc execution of programs expecting command-line arguments. While indeed possible by leveraging more advanced features of the RTSC build model inside your package.bld script, the xdc.tools.loader utility proves ideal for more informal and interactive program execution.

 
 
1
>> xs xdc.tools.loader appSt.x64P      
statically-created instance
0 48 90 118 127 118 90 48 0 -49

 
 
2
>> xs xdc.tools.loader appDy.x86GW     
dynamically-created instance
0 48 90 118 127 118 90 48 0 -49

 
 
3
>> xs xdc.tools.loader appDy.x64P 255  
dynamically-created instance
0 6 12 18 25 31 37 43 49 56

Since we've used identical per-instance config values at line 2 of progSt.cfg and line 2 of progDy.cfg, the output at lines 1 and 2 above should indeed match—even though we've built these programs with different RTSC targets for execution on different RTSC platforms. If, however, we explicitly pass a command-line argument to the apDy program which overrides our pre-configured default for range, we'll then see the different set of outputs at line 3.

Working with instance factories — AppFa

The final leg of our journey takes us to a module named bravo.math2.genapps.AppFa, where we'll leverage RTSC instance factories defined in the meta-domain—but typically used in the target-domain—to create different sorts of IGen instance objects at runtime. As you'll soon see, RTSC instance factories effectively enable one proxy to potentially handle abstract instances originating from multiple delegates—all inheriting a common interface, of course.

First, though, let's look at the spec followed by the target-implementation for AppFa.

bravo/math2/genapps/AppFa.xdc
 
 
 
 
 
 
 

 
 
 
 

 
 
 

 
 
 
 
import stdsorg.math.IGen;
 
/*! IGen app using factory-created instances */
module AppFa {
 
    /*! IGen module used in this app */
    @Factory
    proxy PGen inherits IGen;
 
    /*! PGen factory descriptor */
    struct FctyDesc {
        String name;
        PGen.Factory fcty;
    }
 
    /*! Factory descriptor table */
    FctyDesc fctyDescTab[];
 
    /*! Program entry point */
    Int main(Int argc, Char* argv[]);
 

bravo/math2/genapps/AppFa.c
 
 
 
 
 
 
 
 
 
 
 
 
 
4a
4b
 
 
 
 
 
 
5a
 
 
 
 
 
5b
 
 

 
 

 
 
 
 
 
#include <xdc/runtime/System.h>
#include "package/internal/AppFa.xdc.h"
 
#include <stdlib.h>
 
#define COUNT 10
 
Int AppFa_main(Int argc, Char* argv[])
{
    AppFa_PGen_Handle genInst;
    AppFa_PGen_Params genParams;
    Int i;
 
    AppFa_PGen_Factory* fctyRef = NULL;                /* passed to create */
    AppFa_PGen_Params* prmsRef = NULL;                 /* passed to create */
 
    System_printf("factory-created instance\n");
 
    if (argc > 1) {                                     /* select factory */
        Int k = atoi(argv[1]);
        System_printf("\"%s: \"", &AppFa_fctyDescTab[k]->name);
        fctyRef = &AppFa_fctyDescTab[k]->fcty;        /* passed to create */
    }
 
    if (argc > 2) {                                      /* assign params */
        AppFa_PGen_Params_init(&genParams);
        genParams.range = atoi(argv[2]);
        prmsRef = &genParams;                         /* passed to create */
    }
 
    genInst = AppFa_PGen_create(fctyRef, prmsRef, NULL);
 
    for (i = 0; i < COUNT; i++) {
        System_printf("%d ", AppFa_PGen_next(genInst)); 
    }
    System_printf("\n");
 
    return 0;
}

The special @Factory attribute preceding the proxy declaration at line 1 enables us to reference a built-in Factory type associated with PGen—either as PGen.Factory at line 2 of the module's .xdc file or else as a AppFa_PGen_Factory at line 4a of the module's .c file. As you'll see shortly, a client meta-program would ultimately populate the ApFa.fctyDescTab array spec'd here at line 3 with an appropriate set of PGen.Factory objects paired with a descriptive name.

Turning to the target-domain, pay careful attention to the AppFa_PGen_create call on line 6—a subtle variation from the norm which occurs when using any RTSC proxy declared with the @Factory attribute. As you can see here, this particular form of the Mod_Proxy_create function takes an extra argument of type Mod_Proxy_Factory* that in turn references a statically-initialized Mod_Proxy_Factory assigned in the meta-domain. When passed NULL as its first argument, this form of Mod_Proxy_create operates in the usual manner—deferring directly to the delegate module bound to this proxy.

In the case of AppFa.c, lines 4a and 4b respectively declare and initialize references to a Factory and Params structure associated with the AppFa_PGen proxy. Conditional upon the program receiving sufficient command-line arguments, we've updated these references at lines 5a and 5b. The create call at line 6 then consumes fctyRef and prmsRef, which may still have NULL as their values in some cases.

Compared with our earlier AppDy module, we can now select the factory used to create an abstract IGen instance as well as control its common per-instance configs—all at runtime, of course. To fully grasp the implications of this last statement, however, we need to examine the client's perspective of AppFa within the meta-domain.

Client configuration.  As we hinted earlier, the use of RTSC instance factories would in general enable the AppFa.PGen proxy to create abstract IGen instance object originating from different IGen modules. Depending upon a command-line argument passed to the program at runtime—which selects a particular element from a pre-configured AppFa.fctyDescTab array during execution—the output could originate from SineGen, from RandGen, or from any other module that inherits the IGen interface.

 
 
 
>> xs xdc.tools.loader appFa.x64P 0             
factory-created instance
"sine16": 0 97 181 236 255 236 181 97 0 -98

 
 
 
>> xs xdc.tools.loader appFa.x64P 1             
factory-created instance
"sine32": 0 181 255 181 0 -182 -256 -182 0 181

 
 
 
>> xs xdc.tools.loader appFa.x64P 2             
factory-created instance
"rand10": 126 108 219 76 238 117 133 145 62 236

To effect these results, the following meta-program adds three elements to the AppFa.fctyDescTab array—the first two defining the characteristics of the "sine16" and "sine32" factories, and the third defining the "rand10" factory.

bravo/math2/genapps/progFa.cfg
 
 
 
 
 
1
2
 
 
3
 
4
 
 
 
var AppFa = xdc.useModule('bravo.math2.genapps.AppFa');
var Program = xdc.useModule('xdc.cfg.Program');
var RandGen = xdc.useModule('bravo.math2.RandGen');
var SineGen = xdc.useModule('bravo.math2.SineGen');
 
AppFa.PGen = SineGen;
AppFa.PGen.defaultParams$ = new SineGen.Params({range: 127, freq: 16}};
 
AppFa.fctyDescTab = [
    {name: "sine16", fcty: AppFa.PGen.addFactory$(SineGen, new SineGen.Params({range: 255, freq: 16}))},
    {name: "sine32", fcty: AppFa.PGen.addFactory$(SineGen, new SineGen.Params({range: 255, freq: 32}))},
    {name: "rand10", fcty: AppFa.PGen.addFactory$(RandGen, new RandGen.Params({range: 100, seed: 10}))},
];
 
Program.main = AppFa.main;

As with each of our previous meta-programs, we've configured the AppFa.PGen proxy at lines 1 and 2 with the same set of defaults—in the event that our program receives no command-line arguments at runtime. Recognizing the pattern here—an IGen module paired with a Params object overriding that same module's defaults for per-instance configs—the instance factories synthesized beginning at line 3 actually follow suit.

In general, any proxy carrying the @Factory attribute in its original spec supports a special meta-function named addFactory$ whose arguments expect values of the same types assignable as the proxy's delegate as well as the proxy's defaultParams$ property. In effect, each additional factory behaves like a "mini-proxy" in its own right—pairing the same sorts of information already used at lines 1 and 2 under the same sorts of constraints. And yet—as line 4 illustrates—we can support a mixture of IGen delegate modules under the auspices of a single IGen proxy.

Performance impact.  Mixing IGen delegate modules in this fashion does, however, incur some runtime cost. Until now, all proxies worked with just one delegate, known by the end of program configuration. Though generated automatically, you can imagine the body of most proxy functions simply issuing direct calls to corresponding delegate functions of the same name; most compilers—even without whole-program optimization—can systematically fold-away these proxy functions.

If, however, the proxy must deal with instances originating from factories tied to different delegates, the runtime implementation devolves to the same technique used by every other object-oriented programming language—virtual function tables. So, unlike the call to AppDy_PGen_next at line 5 of AppDy.c, the call to AppFa_PGen_next at line 7 of AppFa.c will not call the SineGen_next function directly; rather, AppDy_Pgen_next will access a function table referenced through its instance handle argument that in turn holds a pointer to the actual next function appropriate for this object—either SineGen_next or RandGen_next in this case. So here's the bottom line—several levels of runtime indirection versus direct calls to delegate functions amenable to auto-inlining.

Fortunately, most applications of the RTSC proxy-delegate pattern do not involve factories; and even when they do, the function table implementation should only kick in when the application requires proxy factories using different delegate modules. Put in other terms, the worse-case implementation of RTSC interfaces achieves parity with current techniques used implicitly by C++ or else explicitly by over-zealous C programmers infatuated with function pointers. But as for the best-case performance of RTSC interfaces—you've already seen how we've benchmarked favorably against hand-tweaked code tuned for a specific target.

See also

 

[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