|
|
@@ -1 +1,121 @@
|
|
|
+(** Desugaring: split variable initialisations, prevent incorrect double
|
|
|
+ evaluation, and transform for-loops to while-loops. *)
|
|
|
+
|
|
|
+(** {2 Split variable initialisations}
|
|
|
+
|
|
|
+ Variable initialisations are splint into declarations and assignments, to
|
|
|
+ generalize the AST format. This makes context analysis easier, since
|
|
|
+ initialisations need not be considered. The assignments are placed in the
|
|
|
+ function body, after local fuction declarations (which are not in the
|
|
|
+ example):
|
|
|
+
|
|
|
+{v int foo = 0;
|
|
|
+int bar = foo; v}
|
|
|
+ becomes:
|
|
|
+{v int foo;
|
|
|
+int bar;
|
|
|
+foo = 0;
|
|
|
+bar = foo; v}
|
|
|
+
|
|
|
+ {3 Array initialisations}
|
|
|
+
|
|
|
+ A more complex class of initialisations are array initialisations. Arrays
|
|
|
+ can be initialised to a scalar value or to an array constant. For array
|
|
|
+ constants that cover all array dimensions, separate assign statements are
|
|
|
+ generated for each array index. For scalar values and aray constants that
|
|
|
+ have a lower nesting level than the number of array dimensions, for-loops
|
|
|
+ are generated. The following example shows all situation:
|
|
|
+{v int[3] a = [1, 2, 3];
|
|
|
+int[3] b = 4;
|
|
|
+int[2, 2] c = [[3, 4], 5]; v}
|
|
|
+
|
|
|
+ The snippet above is transformed to code equivalent to the following:
|
|
|
+
|
|
|
+{v int[] a;
|
|
|
+int[] b;
|
|
|
+int[] c;
|
|
|
+a = __allocate(3);
|
|
|
+a[0] = 1;
|
|
|
+a[1] = 2;
|
|
|
+a[2] = 3;
|
|
|
+b = __allocate(3);
|
|
|
+for (int i = 0, 3) \{
|
|
|
+ b[i] = 4;
|
|
|
+\}
|
|
|
+c = __allocate(4);
|
|
|
+c[0] = 3;
|
|
|
+c[1] = 4;
|
|
|
+for (int i = 0, 2) \{
|
|
|
+ c[1, i] = 5;
|
|
|
+\} v}
|
|
|
+
|
|
|
+ {2 Prevent incorrect double evaluation}
|
|
|
+
|
|
|
+ In the following code:
|
|
|
+{v int twos = 0;
|
|
|
+int two() \{
|
|
|
+ twos = twos + 1;
|
|
|
+ return 2;
|
|
|
+\}
|
|
|
+void foo() \{
|
|
|
+ int[2, two()] a = [[1, two()], [1, two()]];
|
|
|
+ printInt(a[1, 1]);
|
|
|
+\} v}
|
|
|
+
|
|
|
+ [two()] must be evaluated exactly three times in order to preserve correct
|
|
|
+ behaviour. However, [a[1, 1]] will at a later stage be transformed into
|
|
|
+ [a[(1 * two()) + 1]], thus incorrectly evaluating [two()] an additional
|
|
|
+ time. Assigning a scalar value to an array create the same problem, since
|
|
|
+ this is trwnsformed into a for-loop, where the scalar expression is
|
|
|
+ evaluated during each iteration. The problem is solved by creating new
|
|
|
+ so-called "constant variables" for all of these values, and initializing hem
|
|
|
+ to the original expressions. The expressions are then replaced with usages
|
|
|
+ of these variables. A constant variable is a variable that is known to only
|
|
|
+ be assigned once in total. This is only true for some variables generated by
|
|
|
+ the compiler. In the later {!Constprop} phase, these assignments are a found
|
|
|
+ and those which assign constant values (e.g., integers), are propagated to
|
|
|
+ uses of the variable. This way, only the non-constant expressions are
|
|
|
+ defined in new variables in the resulting code.
|
|
|
+
|
|
|
+ For the example above, the result will be (after array dimension reduction
|
|
|
+ and constant propagation):
|
|
|
+{v ...
|
|
|
+void foo() \{
|
|
|
+ int a$dim$$2 = two();
|
|
|
+ int const$$1 = two();
|
|
|
+ int const$$2 = two();
|
|
|
+ int[2, a$dim$$2] a;
|
|
|
+ a = __allocate(2 * a$dim$$2);
|
|
|
+ a[0] = 1;
|
|
|
+ a[1] = const$$1;
|
|
|
+ a[a$dim$$2)] = 1;
|
|
|
+ a[a$dim$$2) + 1] = const$$2;
|
|
|
+ printInt(a[(1 * a$dim$$2) + 1]);
|
|
|
+\} v}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ {2 Transforming for-loops to while-loops}
|
|
|
+
|
|
|
+{v for(int i = <start>,<stop>,<step>) \{
|
|
|
+ <body>
|
|
|
+\} v}
|
|
|
+
|
|
|
+ is transformed into:
|
|
|
+
|
|
|
+{v i$1 = <start>;
|
|
|
+step$2 = <step>;
|
|
|
+stop$3 = <stop>;
|
|
|
+while((step$2 > 0) ? (i$1 < stop$3) : (i$1 > stop$3)) \{
|
|
|
+ <body>
|
|
|
+ i$1 = i$1 + step$2;
|
|
|
+\} v}
|
|
|
+
|
|
|
+ Where [i$1], [step$2] and [stop$3] are fresh variables. Definitions of these
|
|
|
+ new variables are added to the scope of the current function. Every
|
|
|
+ occurrence of [i] in [<body>] is replaced with the fresh variable [i$1],
|
|
|
+ this prevents problems with nested for-loops that use the same induction
|
|
|
+ variable .*)
|
|
|
+
|
|
|
+(** Main phase function, called by {!Main}. *)
|
|
|
val phase : Main.phase_func
|