Black-box composition of mismatched software components

concern This column identifies a shared intention underlying a set of alternative interface conventions. These concerns are the root of our interest in styles: if there were only one way for a given abstract concern to be realised concretely, then there would be no need for composition tools like Cake to support multiple styles. Sample concretion approaches These describe broad equivalence classes of approaches. Within each class, the differences between each approach are relatively superficial. Each group member might therefore be considered a particular parameterisation of one overarching logical style. Note also that these concretion approaches are not mutually exclusive; some styles will combine many of these at the same time. For example, many C library calls report errors by some combination of the return value, the errno global and an error discovery function like ferror(). We will consider both composition and parameterisation of styles in due course. Concrete examples and reference This columns lists real APIs, well-known programming idioms or documented tool implementations instantiating a given concretion approach. Since we are implicitly interested in conventions appearing at the object code level, notice how these concretions descend right down to the binary level, and are a mixture of tooland programmer-selected conventions. For most examples, a bibliographic or section reference to further details is provided. Classification We label each stylistic concern by a subset of mechanism, Dwarf, Platonic, dataflow, structural and runtime. These will be explained inline as we walk through the rows of the table, and are highlighted in bold type when first introduced. 6.3. 2 Abstract concerns Most rows of the table are self-explanatory, but we add the following notes on selected rows. Chapter 6. Extending Cake with component styles 159 Procedure call Local procedure calls may be implemented in a variety of ways using a mixture of registers, the stack and static storage. The mechanism tag remarks that these lie below Cake’s baseline abstraction level; the Cake language has no means to describe them (§3.4). However, a Cake implementation may support them using mechanismspecific knowledge. Remote procedure calls are usually rendered as interprocess communication system calls, with arguments and return values encoded in particular ways. Subsequent rows cover these argument encodings. Error reporting Certain data values indicate errors; we may need to gather them from diverse sources (return values, error discovery functions, etc.), and decode them using various conventions. The tag Platonic notes that since error return is a concept builtin to the Cake stub language, Cake defines a standardised abstract interface which any style definition addressing this concern should satisfy. The “mechanism” tag also applies, specifically to exception handling, because this is generally implemented by intricate stack manipulations which, like calling conventions, lie below Cake’s level of abstraction. The data flow tag denotes that error conventions are a concern which can be directly observed in the trace of data flowing across an interface; we explain the significance of this at the end of this section. Name mangling and word-separating The Cake compiler performs name-matching. Different ways of encoding the same names therefore represent a stylistic concern which Cake should abstract. Often, demangled names are available in debugging information, so do not require separate user-provided description. However, ad-hoc naming and wordseparating conventions could usefully be interpreted in the Cake compiler, much like pattern-based Cake rules (§2.2.11), allowing more correspondences to be formed automatically. Unstructured and composite values Encodings of unstructured and structured data are described by Dwarf, and Cake inherits an understanding of these. However, not all stylistic alternatives are supported: for example, string-based encodings of values would require extending Dwarf with some sort of grammar-based description of variablelength encodings. Dimensions which would require Dwarf extensions to be captured within Cake are tagged with Dwarf in the Classification column. We use “functionally abstracted” to denote interfaces, like getter and setter interfaces in this example, which hide access to state behind function calls; it need not indicate a functional programming style per se. Call demultiplexing Polymorphic code [Strachey 1967] invariably embodies a concretion process in which an abstract operation is mapped to one of many concrete implementations. We call this process “demultiplexing”. Examples include static overload resolution [Stroustrup 1997], virtual function calls (demuxed by an indirect table lookup), and other, more flexible dispatch processes [Lindholm and Yellin 1999; Alpern et al. 2001; DeMichiel and Gabriel 1987; Chambers 1992; Clifton et al. 2000]. User-level solutions also exist, such as a simple switch statement (e.g. on a “type” or similar field, as 160 6.3. Dimensions of stylistic variation Chapter 6. Extending Cake with component styles 161 Table 6.1: Dimensions of stylistic variation, with examples 162 6.3. Dimensions of stylistic variation seen in C code) or the complex Visitor pattern [Gamma et al. 1995]. Components may be mismatched in what demultiplexing is performed (e.g. on what arguments), how (i.e. by what mechanism), how much (i.e. how fine-grained) and by whom (caller, callee or an intermediary). Many implementations yield nontrivial relationships between families of calls (e.g. “all polymorphic calls in class C”) and the data structures used to demultiplex those calls (e.g. “the vtable for class C”). This explains the structural tag: demultiplexing concretions often rely on static structural constraints which cannot be expressed in familiar Cake rules. Call multiplexing Demultiplexing’s converse is where a single physical call provides access to multiple logically distinct operations. This multiplexing is usually simple, in that the logical call is encoded fairly directly, for example as an argument, as with Unix’s ioctl() [IEEE POSIX, 1988], or else Unix’s execve() where the logical operation is encoded as a command and its arguments. Object initialization Initialization calls show great stylistic variation. This may be integrated with memory allocation, as with the C++ new operator, or not, as with C APIs reliant on malloc(). This concern is labelled runtime because it has a special interaction with the Cake runtime: since Cake rules require the runtime to initialize and update logical replica objects (§4.4.4), knowledge of this style can ensure that correct initialization procedures are followed for runtime-created objects (e.g. invoking a relevant C++ constructor). Contextual initialization Object initialization often brings obligations to initialize or somehow update state in the object’s environment. For example, many APIs require that certain objects are “registered” by calls on other objects before they are usable, in a separate step from the object’s own initialization. The p2k study in Chapter 5 discovered a fragment of C code which could not be reimplemented in Cake precisely because of registration calls. Similarly, dependency constraints on initialization are common, meaning some objects must be initialized before some other may be created or used. Formatted object representation Many components can generate human-readable representations of objects. These may be captured by language-defined conventions such as Java’s toString() method or C++’s overloadable ¡¡ operator. They may also be ad-hoc, e.g. a print ¡data type¿() function in C code. Synchronisation Abstract requirements such as “ensure this sequence of operations appears atomic” or “wait until condition X becomes true” are communicated across many concrete interfaces. Often these use fairly simple functional abstractions (e.g. Unix’s pthread mutex lock() and family), but occur in very complex patterns of use. Run-time self description Many languages allow introspectively discovering descriptions (or “type information”) of objects. In object code these appear as accesses to Chapter 6. Extending Cake with component styles 163 compiler-inserted fields or calls to runtime functions. In other languages, some libraries layer descriptive information into data structures, by adopting particular layout conventions and defining related functions, as in the GObject library [Krause 2007]. These bring structural constraints, since they depend on a systematic static embedding of descriptive information. They are also useful to the Cake runtime, as they provide mechanisms for discovering object descriptions which might be more efficient or precise than Cake can discover on its own (§4.3.2). Abstract resource management Many programs consume abstract resources, such as file handles, network connections, mutexes, slots in a fixed-size data structure, and so on. Management of these is distinct from memory management. For instance, a garbage collector will not release them automatically without special instruction. Interface conventions for management functions show considerable stylistic variation. The Microsoft CLR [Meijer and Gough 2001] defines a special IDispose interface for objects encapsulating abstract resources; calling this correctly is the programmer’s responsibility. Meanwhile, the “resource acquisition is initialization” style in C++ works by ensuring that abstract resources’ lifetimes are tied to an object’s.resource management Many programs consume abstract resources, such as file handles, network connections, mutexes, slots in a fixed-size data structure, and so on. Management of these is distinct from memory management. For instance, a garbage collector will not release them automatically without special instruction. Interface conventions for management functions show considerable stylistic variation. The Mi