• No results found

Runtime Type Identification

In document Extensible Computer Music Systems (Page 97-106)

2.5 Conclusions

3.2.5 Runtime Type Identification

Runtime Type Identification (RTTI) is the ability to identify the type of a variable at runtime. The facility for RTTI exists in numerous languages. Examples for C++, Java, Python, Ruby, and Common Lisp are shown in Table 3.1. Language Syntax C++ typeid(*ptr) Java obj.getClass() Python type(obj) Ruby obj.class Common Lisp (type-of v)

In general programing languages such as those given in Table 3.1, RTTI is useful to conditionally branch to perform work, depending on what type of data has been given to a function. In Csound, opcodes are written using RTTI so that one implementation of a function can be used to cover multiple combinations of argument types. This would be the case if the same C function was used with multiple OENTRYs for an opcode.

However, the pre-Csound6 RTTI system was problematic. The system only worked to discern two types – a and S – and the implementation code was not clear to read, maintain, or use. In Csound6, with the introduction of the new type system, a more formal implementation of RTTI was introduced. This simplified RTTI as a whole as well as made it work for all types found in Csound.

The following will start by discussing the evolution of RTTI in Csound prior to Csound6. Next, it will discuss the technical limitations of the pre- Csound6 RTTI implementation. Finally, the new implementation in Csound6 will be described.

Evolution of RTTI pre-Csound6

Csound1988 In Csound1988, a field in OPTXT called xincod was used to track the type of input arguments to an opcode. This value was an integer that was used both as a bit-flag and as an index. When the parser read in Orchestra code, for each opcode statement, it would set the the second or first bit of xincod to 1 if the corresponding first or second argument for the opcode was of type a. This marking would end up with xincod being a value between 0 and 3.

At the time, opcodes were defined in ENTRY data structures.7 ENTRYs had

up to four aopadr function pointers defined. This allowed one opcode ENTRY to specify x types for its input argument types, where the x would mean “k- or a-type argument”. The four aopdr functions would then correspond to the four variations allowed for the two “xx” arguments: kk, ka, ak, or aa. This allowed a single opcode like oscil to be specified once, but accommodate working with different variations of input types.

At runtime, when a new instance of an instrument was created, the xincod value would be used as an index into the ENTRY’s aopadr array to determine which function to use. Listing 3.25 shows the definition of the ENTRY data structure with its aopadr array. It is followed by the ENTRY for oscil, which uses x-types and defines multiple aopadr functions. Next shows the code in rdorch.c that shows the marking of xincod in the parser. Finally, the code from oload.c shows the use of xincod to determine which performance function from the aopadr array to use for an opcode.

// cs.h :101

typedef struct entry { char * opname ; int dsblksiz ; int thread ; char * outypes ; char * intypes ; SUBR iopadr ; SUBR kopadr ; SUBR aopadr [4]; } ENTRY ;

// entry .c :195

{ " oscil ", S( OSC ), 11, "s", " xxio ", oscset , koscil , osckk , oscka , oscak , oscaa },

// rdorch .c :392

if ( tfound == 'a' && n < 2) tp -> xincod += 2-n;

// oload .c :211

else opds -> opadr = ep -> aopadr [ttp -> xincod ];

Listing 3.25: Use of xincod field in Csound (1988)

Csound5 By the time of Csound5, the usage of xincod had changed. Firstly, xincod was still used as a bit-flag, but all input arguments were marked whether they were an a-type or not, up to the size of xincod. The first bit would correspond to the first argument, the second bit to the second argument, and so on. This allowed the first sixteen arguments to be marked as xincod was a 16-bit integer.

Secondly, the use of an aopadr array was abandoned. This was largely to accommodate the use of more than two arguments that might be of a-type. If the previous system was maintained, the potential number of variations would require an aopadr array to be of size 65536 and many functions to be implemented.

Instead, an aopadr field was added to OPTXT that could be dynamically set at runtime. Also, functions were modified to read in xincod at runtime to use different branches of code depending on if an argument was an a-type or not. Listing 3.26 shows an example use of xincod in Csound5. Here, the buzz opcode is specified with an input argument specification of “xxkio”. During

performance, at init-time, the buzz opcode’s init function performs a check of xincod and caches whether its amp and cps arguments were set to a-type arguments or not. At performance-time, the buzz opcode’s performance function would first do a single calculation as-if an argument was a scalar value (i.e, k- or i-type), but then check if arguments were a-type within its performance loop and conditionally do further processing.

// Engine / entry1 .c :399

{ " buzz ", S( BUZZ ), TR |5, "a", " xxkio ", bzzset , NULL , buzz },

// rdorch .c :1945

// csound_orc_compile .c :313

if ( tfound == 'a' && n < 31) /* JMC added for FOG */ /* 4 for FOF , 8 for FOG ; expanded to 15 */ tp -> xincod |= (1 << n);

// H/ csoundCore .h :115

# define XINCODE ORTXT . xincod # define XINARG1 (p-> XINCODE & 1) # define XINARG2 (p-> XINCODE & 2)

// OOps / ugens4 .c :38

p-> ampcod = ( XINARG1 ) ? 1 : 0; p-> cpscod = ( XINARG2 ) ? 1 : 0;

// OOps / ugens4 .c :83 if (p-> ampcod )

scal = *++ ampp * over2n ; if (p-> cpscod )

inc = ( int32 ) (*++ cpsp * sicvt2 );

In addition to xincod, type tracking was extended to use three other fields: xoutcod, xincod_str, and xoutcod_str. Each of these were used as bit-flags in the same way xincod was used. These fields were used to track if output arguments were an a-type, or if input or output arguments were S-types, respectively.

Analysis The system of xincod, xoutcod, xincod_str, and xoutcod_str for tracking type information was useful for simplifying opcode writing. How- ever, looking towards the future, the system of using bits and adding new bit-flag fields per-type would not scale. If a developer wanted to add tracking for new types, they would have to add new fields and add further code for checking and setting bit-flags. Also, the bit-flag system required that the system itself have prior knowledge of the type it was trying to track. This would be an impossible situation to track types defined in a third-party plugin using the new type system. Due to the limitations above, as well as the difficulty in understanding the code, a new system was devised.

RTTI in Csound 6

In Csound 6.04, I introduced a new system for generic RTTI for any opcode input or output argument. All bit-flags and tracking code were removed. In its place, all variables used in Csound had their corresponding CS_TYPE set in memory at a negative offset from the data pointer. This change affected how memory was calculated and laid out for instrument instances as well as how the Csound channel database was allocated and managed. Also, Csound API methods were added to simplify interrogating an argument’s types.

A comprehensive discussion of memory layout for instruments, variables, and opcodes was presented in [202]. That work was produced before this

work for RTTI was introduced. Figure 3.1 shows the layout of memory for an instrument instance pre-Csound6. Memory is laid out in three main regions: the instrument header (an INSDS data structure), the variable memory space, and the opcode memory space. For instrument instances, the sum total of variable memory for an instrument instance was previously calculated as total of the sizes for each variable’s type. For example, k-type arguments are defined as a single MYFLT instance.8 If an instrument had 3 k-type variables, each

instrument instance would allocate 3 * sizeof(MYFLT) amount of memory for the variables used. The memory allocated for the variables would then be partitioned with the address of each partition assigned to the input and output argument pointers for each opcode instance.

Figure 3.1: Memory layout diagram for pre-RTTI Csound instrument instance. With RTTI, the memory allocation strategy was modified to include an additional sizeof(CS_TYPE*) for each variable. For the example above, the

8In Csound, MYFLT is a macro assigned to eitherfloat or double. This allows Csound

to be compiled to use 32-bit or 64-bit numeric floating point precision for processing. The default for Csound 6 is to usedouble.

memory allocated would be 3 * (sizeof(MYFLT) + sizeof(CS_TYPE*)). Also, the process of dividing up the total variable memory for an instrument instance consequently changed. The memory would now be interpreted as alternating pointers to CS_TYPE and variable memory, as shown in Figure 3.2.

Figure 3.2: Memory layout diagram for Csound instrument instance with RTTI.

When assigning variable memory to opcodes, the system partitions the variable memory space and casts the partitions to CS_VAR_MEM* pointers. The members of the CS_VAR_MEM data structure, shown in Listing 3.27, are used within the system to clarify the intention of the code and express how the block of memory is being used. Casting memory as a CS_VAR_MEM does have one drawback in that compilers may align data structure members differently and introduce padding to the member data’s addresses. To deal with this situation, a CS_VAR_TYPE_OFFSET is calculated in a low-level way to account

for any potential alignment issues. The calculated offset can then be used to find the address of the CS_TYPE for any given variable’s data pointer.

// include / csound_type_system .h :51 typedef struct csvarmem {

CS_TYPE * varType ; MYFLT value ; } CS_VAR_MEM ;

// include / csound_type_system .h :54

# define CS_VAR_TYPE_OFFSET ( sizeof ( CS_VAR_MEM ) - sizeof ( MYFLT ))

// Top / csound .c :3747

/** Returns the CS_TYPE for an opcode 's arg pointer */ CS_TYPE * csoundGetTypeForArg ( void * argPtr ) {

char * ptr = ( char *) argPtr ;

CS_TYPE * varType = *( CS_TYPE **) ( ptr - CS_VAR_TYPE_OFFSET ); return varType ;

}

Listing 3.27: RTTI-related code in Csound 6

With the new RTTI system, opcodes read and write values to and from arguments in the same exact way as before this work. For opcodes that did not use RTTI, no changes were necessary. For opcodes that do use RTTI, the information retrieved for an argument was no longer based on argument index, but directly retrieved from the the data pointer for the argument using the csoundGetTypeForArg() function (shown in Listing 3.27). The developer would not have to remember which argument mapped to what index as they would have with the bit-flag system; rather, he would just ask of Csound what

is the type for the argument. This, together with looking at the CS_TYPE, has arguably led to simpler and easier to read code.

The new RTTI system in Csound6 is a simpler, more robust, and more extensible system than previous implementations. It provides a generic solution to retrieve the type for any variable. All arguments for opcodes now have their CS_TYPE available at runtime. Any new types will automatically be used as part of the system, freeing developers to create opcodes that use RTTI with new types without additional tracking work. Since 6.04, the new RTTI system has been employed and the previous RTTI-related code is now removed from the system.

In document Extensible Computer Music Systems (Page 97-106)