Cyclone (summary) Jernej Barbic, 15-213, Sp 06, Section F ======================================= Cyclone is a C-like language. It's C with some extra syntax. Developed in 2000, by Cornell University and AT&T Research Labs. Cyclone advantages: + type-safe + avoids common C programming errors Cyclone disadvantages: - slower program execution than C (factor of 1.6 on a certain benchmark) - slightly larger memory requirements (about 7% on a certain benchmark) (however, overheads significantly smaller than those of Java) Ordinary gcc won't compile Cyclone code. To compile a Cyclone program, first use the Cyclone compiler to translate Cyclone code to C, and then use a C compiler (such as gcc). Fat pointers ------------ Ordinary C pointers are unrestricted: programmers can assign them to point to arbitrary locations in memory. In contrast, a fat pointer is a special kind of a Cyclone pointer that can only point to memory locations inside a certain memory range (lower and upper bound). These bounds are specific to every fat pointer and are NOT known at compile time. They only become known at runtime, such as when memory is dynamically allocated on the heap: float *@fat p = (float *@fat) malloc (sizeof(float) * 1024); // p is a fat pointer pointing to the heap Shorthand: float ? p = (float ?) malloc (sizeof(float) * 1024); Fat pointers are useful for arrays whose size is not known at compile time. Pointer arithmetic on p is possible within the pointer bounds. To check the bounds, Cyclone inserts runtime checks into the code. A runtime exception is raised if such a runtime check fails. Runtime checks prevent unsafe memory accesses. The cost is a slight loss of performance. Bounded pointers ---------------- These are like fat pointers, except that the lower and upper bounds ARE known at compile time. This is possible if arrays have fixed sizes, known at compile time: float x[4] = {1.0, 2.0, 3.0, 4.0}; float *@numelts(4) p = x; // p is a bounded pointer With bounded pointers, the Cyclone compiler can perform more optimizations than with fat pointers, so the code runs faster. Pointer arithmetic can be performed within the static bounds. Example: p[0],p[1],p[2],p[3],*p,*(p+1),*(p+2),*(p+3) are valid, but p[4] is not and will not compile. Thin pointers ------------- These are simply bounded pointers with numelts(1). What can we do with such a pointer? We can dereference it: *p Invalid: *(p+1) We can point it to another variable: float x = 2.718281828; float *@numelts(1) p = &x; We can make it equal another thin pointer: float *@numelts(1) q = p; We can traverse a linked list with it (example given in Cyclone lecture). Non-NULL pointers ----------------- These are pointers that can never be assigned NULL. Non-NULLness can be combined with other attributes (thin pointer, bounded pointer, etc.). float x[4] = {1.0, 2.0, 3.0, 4.0}; float *@notnull p = x; // p cannot be NULL An exception is raised if the pointer is assigned a NULL value at runtime. How are non-NULL pointer useful? If a function is passed a @notnull pointer as an input parameter, then the function knows that the pointer can never be NULL, so it doesn't have to check it: void foobar(float *@notnull p) { // foobar can assume that p != NULL ... } Tagged unions ------------- In standard C, unions are not type-safe. The problem occurs if the program writes to a union under one type, and then reads back from the union under a different type. Sometimes, this is actually not a problem, for example, if the programmer is knowingly performing some low-level hacking. But in most cases, it's actually an unintentional programming error. Example of valid C code that compiles without any warnings (even with -Wall): #include union simpleUnion { int a; float b; } mySimpleUnion; int main() { float x; mySimpleUnion.a = 42; x = mySimpleUnion.b; // type-unsafe memory access printf("%f\n",x); return 0; } Of course, these problems can be avoided by careful programming. Cyclone provides an automatic way of detecting such unsafe accesses: tagged unions: @tagged union simpleUnion { int a; float b; } mySimpleUnion; With tagged unions, Cyclone inserts runtime checks into the code to ensure that if a union member is read, that member was the last member written. For example, if the main() routine from above was used in Cyclone with a tagged union, an exception would be raised at the line: x = mySimpleUnion.b; How are tagged unions implemented? Using tags (hence the name "tagged union"): A tagged union is actually a structure: it's the union plus a tag. Cyclone automatically maintains the tag field, so that it always corresponds to the type of the variable currently contained in the union. Schematic example of how a tagged union is implemented: enum tag { Int, Float }; union simpleUnion { int a; float b; }; struct taggedUnion { enum tag t; union simpleUnion u; } mySimpleUnion; A Cyclone programmer can query the current type of the tagged union at runtime: if (mySimpleUnion.t == Int) printf("The union currently contains an integer.\n"); else printf("The union currently contains a float.\n"); Of course, the presence of the tag means that a tagged union occupies more space in memory than a standard union. Also, due to runtime checks, the code runs slightly slower. Often, however, the benefits of type-safety (fewer bugs) outweigh performance losses.