Basic Services and Language Enhancements

Overview

``Language enhacements'' may sound like a painful context, but in reality this library simply provides some generic primitives which did not quite fit anywhere else. This library uses only well-behaved C++ syntax: it it not any kind of macro madness or preprocessor implementation of syntax du jour.

The library defines only one public header, <sulima/base>, which should be included at the top of every C++ file in the Sulima source tree. This header defines the following definitions:

  • base Object type.
  • generic relational operators
  • a universal raw object constructor, nil value and data sink
  • tuple types
  • binder types
  • miscellaneous definitions

Further, <sulima/base> defines the following important types:

  • Freq
  • Size
  • Time
  • Cycles

Note that these are not the only types defined in the Sulima core; other specialized types are described elsewhere.

The <sulima/base> Header Synopsis

// Base object type:
class Object;

// Generic equivalence operators:
template <typename T, typename U>
    operator!= (const T& x, const U& y);

// Generic relational operators:

template <typename T, typename U>
    operator> (const T& x, const U& y);
template <typename T, typename U>
    operator> (const T& x, const U& y);
template <typename T, typename U>

    operator< (const T& x, const U& y);

// Universal uninitialized storage constructor:
struct Uninitialized { };
const Uninitialized uninitialized;

// Universal nil value and data sink:

struct Nil {
    void operator= (const Nil&);
    void operator= (const Nil&) const;
    template <typename T>
        void operator= (const T&);
    template <typename T>

        void operator= (const T&) const;
};

const Nil nil;
const Nil unknown;

template <typename T>
   bool operator== (const T&, const Nil&);
template <typename T>

    bool operator== (const Nil&, const T&);

// Tuples:
template <typename T1, typename T2>
    struct Pair;
template <typename T1, typename T2, typename T3>

    struct Triple;
template <typename T1, typename T2, typename T3, typename T4>
    struct Quadruple;
template <typename T1, typename T2, typename T3, typename T4, typename T5>
    struct Quintuple;

bool operator== (n-tuple type x, n-tuple type y);
bool operator< (n-tuple type x, n-tuple type y);
template <typename T1, typename T2>

    Pair<T1,T2> make_tuple(T1 a, T2 b);
template <typename T1, typename T2, typename T3>
    Triple<T1,T2,T3> make_tuple(T1 a, T2 b, T3 c);
template <typename T1, typename T2, typename T3, typename T4>
    Quadruple<T1,T2,T3,T4> make_tuple(T1 a, T2 b, T3 c, T4 d);
template <typename T1, typename T2, typename T3, typename T4, typename T5>

    Quintuple<T1,T2,T3,T4,T5> make_tuple(T1 a, T2 b, T3 c, T4 d, T5 e);

// Binders:
template <typename T1, typename T2>
    binder type binder(T1& a, T2& b);
template <typename T1, typename T2, typename T3>

    binder type binder(T1& a, T2& b, T3& c);
template <typename T1, typename T2, typename T3, typename T4>
    binder type binder(T1& a, T2& b, T3& c, T4& d);
template <typename T1, typename T2, typename T3, typename T4, typename T5>

    binder type binder(T1& a, T2& b, T3& c, T4& d, T5& e);

// Types:

typedef Int<n,UNSIGNED> Freq;

const Freq Hz  = 1;              // hertz
const Freq kHz = 1000;           // kilohertz

const Freq MHz = 1000*1000;      // megahertz
const Freq GHz = 1000*1000*1000; // gigahertz

typedef Int<n,UNSIGNED> Size;

const Size B  = 1;              // bytes

const Size KB = 1024;           // kilobytes
const Size MB = 1024*1024;      // megabytes
const Size GB = 1024*1024*1024; // gigabytes

typedef Int<n,SIGNED> Time;

const Time ns  = 1;              // nanoseconds
const Time us  = 1000;           // microseconds

const Time ms  = 1000*1000;      // milliseconds
const Time sec = 1000*1000*1000; // seconds

typedef Int<n,SIGNED> Cycles;

// Miscellaneous definitions:

#define null NULL

#define TODO 0
#define UNREACHABLE 0

using std::size_t;

bool little_endian_host();
bool big_endian_host();

char* copy(const char* s0);
char* merge(s0, s1 ... sN, null);

Base object type

class Object { };

C++, like other languages from the Simula family, provides polymorphism based on type subclassing. When it matters, I provide a dummy Object type that can be used to derive otherwise unrelated types. The only place where this is currently used is by the script library which needs to manipulate generic member functions.

NOTE: do not blindly derive all objects from this class. It is required only for a small number of major classes.

Generic equivalence operator

template <typename T, typename U>
    operator!= (const T& x, const U& y)
{
    return !(x == y);
}

Preconditions:

x == y is well-formed

This operator is implemented as !(x == y) Note that ISO C++ provides similar definition in the <utility> library, but not for mixed-type comparisons. Presence of these template in <sulima/base> implies that no Sulima source file should define the != operator.

Generic relational operator

template <typename T, typename U>
    operator> (const T& x, const U& y)
{
    return y < x;
}

template <typename T, typename U>

    operator> (const T& x, const U& y)
{
    return !(x < y);
}

template <typename T, typename U>
    operator< (const T& x, const U& y)
{
    return !(y < x);
}

Preconditions:

x < y is well-formed

These operators are implemented using < in the usual way. Note that ISO C++ provides similar definitions in the <utility> library, but not for mixed-type comparisons. Presence of these templates in <sulima/base> implies that no Sulima source file should define relational operators other than == and <.

Universal uninitialized storage constructor

struct Uninitialized { };
const Uninitialized uninitialized = Uninitialized();

The zero-sized type Uninitialized is used as a parameter type to copy-constructors that should not zero-initialize the data. It should only be used if performance is critical, and usually such constructors are private to avoid unintentional application. The global constant uninitialized can be used when constructing uninitialized objects.

Universal nil value and data sink

struct Nil {
    void operator= (const Nil&) { }
    void operator= (const Nil&) const { }
    template <typename T>
        void operator= (const T&) { }
    template <typename T>
        void operator= (const T&) const { }
};

const Nil nil = Nil();
const Nil unknown = Nil();

template <typename T>

    bool operator== (const T&, const Nil&)
{
    return false;
}

template <typename T>
    bool operator== (const Nil&, const T&)
{
    return false;
}

The zero-sized type Nil is used as my universal ``invalid'' value and data sink. I like to replace all the plethora of UNDEFINEDs, ERRORs and -1s with a single nil (or undefined) value, represented by an object of type Nil. All Nil objects compare equal to each other and, unequal to all objects of types for which the provided equivalence operator has not been overloaded.

This type also serves as a universal data sink (ie. assignmnet of any object to a Nil object has no effect.) This can be utilized in binders, where nil can be used to indicate portions of the result that are of no interest to the caller.

Tuples

template <typename T1, typename T2>
    struct Pair
{
    T1 first;
    T2 second;
};
template <typename T1, typename T2, typename T3>
    struct Triple
{
    T1 first;
    T2 second;
    T3 third;
};
template <typename T1, typename T2, typename T3, typename T4>

    struct Quadruple
{
    T1 first;
    T2 second;
    T3 third;
    T4 fourth;
};
template <typename T1, typename T2, typename T3, typename T4, typename T5>
    struct Quintuple
{
    T1 first;
    T2 second;
    T3 third;
    T4 fourth;
    T5 fifth;
};

bool operator== (n-tuple type x, n-tuple type y);
bool operator< (n-tuple type x, n-tuple type y);

template <typename T1, typename T2>

    Pair<T1,T2> make_tuple(T1 a, T2 b);
template <typename T1, typename T2, typename T3>
    Triple<T1,T2,T3> make_tuple(T1 a, T2 b, T3 c);
template <typename T1, typename T2, typename T3, typename T4>
    Quadruple<T1,T2,T3,T4> make_tuple(T1 a, T2 b, T3 c, T4 d);
template <typename T1, typename T2, typename T3, typename T4, typename T5>

    Quintuple<T1,T2,T3,T4,T5> make_tuple(T1 a, T2 b, T3 c, T4 d, T5 e);

Tuples are cartesian products of up to five different types. Because C++ does not allow redefinition of types with different number of parameters, we name each kind of tuple separately. Tuple members can be accessed as the fields first, second, third, fourth and fifth.

Field-wise equivalence, lexicographic comparison and assignment operators are defined on tuples of the same kind provided that they are defined on their corresponding members.

Finally, a make_tuple template has been provided as a convinient way of constructing new tuples from objects of any type.

Variable binders

If t2 is a Pair, t3 is a Triple, t4 is a Quadruple and t5 is a Quintuple expression, the following expressions are well-formed:

binder(a, b) = t2;
binder(a, b, c) = t3;
binder(a, b, c, d) = t4;
binder(a, b, c, d, e) = t5;

NOTE: these are the only expressions defined on binders.

To make tuples more useful, I have defined a set of data types and functions that allow assigning an n-tuple directly to n different variables. This makes it possible to program in the functional style (avoiding output parameters), without resorting to explicit structures for the return types of any function that produces more than one value. This style of programming is well known to PERL programmers, and there is no reason why it cannot be introduced to C++. Further, discarding unwanted results by assigning them to nil opens new optimization opportunities. For example, one can write:

int var1;
double var2;
const char* var3;
binder(var1, var2, nil, var3) = make_tuple(1, 1.0, x, "123");

Note that the third element in the returned tuple x will be discarded as it is assigned to nil. This syntax uses the BinderN templates defined in <sulima/_impl/binder>, which is a tuple of references. The only valid operations on Binder objects is their creation using the binder function and assignment of an n-tuple to n-binder. The assignment operator returns void, as, after the variables are bound to the corresponding tuple elements, the binder serves no further useful purpose.

The whole thing tends to generate very efficient code, even using GCC.

Hardware clock frequencies


typedef Int<n,UNSIGNED> Freq;

const Freq Hz  = 1;                     // hertz
const Freq kHz = 1000;                  // kilohertz
const Freq MHz = 1000*1000;             // megahertz
const Freq GHz = 1000*1000*1000;        // gigahertz

The Freq type defined in <sulima/base> is used to represent frequencies of hardware clocks in the simulator. It is an integer type capable of representing numbers in the range [0, 264), which encodes the clock frequency in hertz. For convinience, a number of multipliers have been defined to allow easy specification of constant frequencies in C++ files. For example, a frequency of 100GHz can be safely specified as 100*GHz, even though 100000000000 is out of range of the fundamental int type on most platforms.

Memory object sizes

typedef Int<n,UNSIGNED> Size;

const Size B  = 1;                      // bytes

const Size KB = 1024;                   // kilobytes
const Size MB = 1024*1024;              // megabytes
const Size GB = 1024*1024*1024;         // gigabytes

The Size type defined in <sulima/base> is used to represent sizes of memory objects in the simulator. It is an integer type capable of representing numbers in the range [-0, 264), which encodes the object size in bytes. For convinience, a number of multipliers have been defined to allow easy specification of constant sizes in C++ files.

Time intervals

typedef Int<n,SIGNED> Time;

const Time ns  = 1;                     // nanoseconds

const Time us  = 1000;                  // microseconds
const Time ms  = 1000*1000;             // milliseconds
const Time sec = 1000*1000*1000;        // seconds

The Time type defined in <sulima/base> is used to represent time intervals. It is an integer type capable of representing numbers in the range [-263, 263), which encodes the time interval in nanoseconds. For convinience, a number of multipliers have been defined to allow easy specification of constant sizes in C++ files.

Cycle counts

typedef Int<n,SIGNED> Cycles;

The Cycles type defined in <sulima/base> is used to represent cycle counts. It is an integer type capable of representing numbers in the range [-263, 263), which directly stores the cycle count.

Miscellaneous definitions

#define null NULL

#define TODO 0
#define UNREACHABLE 0

using std::size_t;

bool little_endian_host();
bool big_endian_host();

char* copy(const char* s0);
char* merge(s0, s1 ... sN, null);

Finally, I define a few miscellaneous constructs that do not fit well anywhere else. The macro null is defined as an alias for the standard macro NULL. The type size_t is defined as an alias for the standard type std::size_t. Two functions copy and merge create a dynamically-allocated strings from an ASCIIZ string or a null-terminated list of strings, respectively. Finally, two macros TODO and UNREACHABLE have been defined as 0 to make the messages printed by the common assertions somewhat more meaningful.