1 //: A big convenience high-level languages provide is the ability to name memory
  2 //: locations. In Mu, a transform called 'transform_names' provides this
  3 //: convenience.
  4 
  5 :(scenario transform_names)
  6 def main [
  7   x:num <- copy 0
  8 ]
  9 +name: assign x 1
 10 +mem: storing 0 in location 1
 11 
 12 :(scenarios transform)
 13 :(scenario transform_names_fails_on_use_before_define)
 14 % Hide_errors = true;
 15 def main [
 16   x:num <- copy y:num
 17 ]
 18 +error: main: tried to read ingredient 'y' in 'x:num <- copy y:num' but it hasn't been written to yet
 19 # todo: detect conditional defines
 20 
 21 :(after "Transform.push_back(compute_container_sizes)")
 22 Transform.push_back(transform_names);  // idempotent
 23 
 24 :(before "End Globals")
 25 map<recipe_ordinal, map<string, int> > Name;
 26 
 27 //: the Name map is a global, so save it before tests and reset it for every
 28 //: test, just to be safe.
 29 :(before "End Globals")
 30 map<recipe_ordinal, map<string, int> > Name_snapshot;
 31 :(before "End save_snapshots")
 32 Name_snapshot = Name;
 33 :(before "End restore_snapshots")
 34 Name = Name_snapshot;
 35 
 36 :(code)
 37 void transform_names(const recipe_ordinal r) {
 38   recipe& caller = get(Recipe, r);
 39   trace(9991, "transform") << "--- transform names for recipe " << caller.name << end();
 40   bool names_used = false;
 41   bool numeric_locations_used = false;
 42   map<string, int>& names = Name[r];
 43   // store the indices 'used' so far in the map
 44   int& curr_idx = names[""];
 45   ++curr_idx;  // avoid using index 0, benign skip in some other cases
 46   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
 47   ¦ instruction& inst = caller.steps.at(i);
 48   ¦ // End transform_names(inst) Special-cases
 49   ¦ // map names to addresses
 50   ¦ for (int in = 0;  in < SIZE(inst.ingredients);  ++in) {
 51   ¦ ¦ reagent& ingredient = inst.ingredients.at(in);
 52   ¦ ¦ if (is_disqualified(ingredient, inst, caller.name)) continue;
 53   ¦ ¦ if (is_numeric_location(ingredient)) numeric_locations_used = true;
 54   ¦ ¦ if (is_named_location(ingredient)) names_used = true;
 55   ¦ ¦ if (is_integer(ingredient.name)) continue;
 56   ¦ ¦ if (!already_transformed(ingredient, names)) {
 57   ¦ ¦ ¦ raise << maybe(caller.name) << "tried to read ingredient '" << ingredient.name << "' in '" << to_original_string(inst) << "' but it hasn't been written to yet\n" << end();
 58   ¦ ¦ ¦ // use-before-set Error
 59   ¦ ¦ ¦ return;
 60   ¦ ¦ }
 61   ¦ ¦ int v = lookup_name(ingredient, r);
 62   ¦ ¦ if (v >= 0) {
 63   ¦ ¦ ¦ ingredient.set_value(v);
 64   ¦ ¦ ¦ // Done Placing Ingredient(ingredient, inst, caller)
 65   ¦ ¦ }
 66   ¦ ¦ else {
 67   ¦ ¦ ¦ raise << maybe(caller.name) << "can't find a place to store '" << ingredient.name << "'\n" << end();
 68   ¦ ¦ ¦ return;
 69   ¦ ¦ }
 70   ¦ }
 71   ¦ for (int out = 0;  out < SIZE(inst.products);  ++out) {
 72   ¦ ¦ reagent& product = inst.products.at(out);
 73   ¦ ¦ if (is_disqualified(product, inst, caller.name)) continue;
 74   ¦ ¦ if (is_numeric_location(product)) numeric_locations_used = true;
 75   ¦ ¦ if (is_named_location(product)) names_used = true;
 76   ¦ ¦ if (is_integer(product.name)) continue;
 77   ¦ ¦ if (names.find(product.name) == names.end()) {
 78   ¦ ¦ ¦ trace(9993, "name") << "assign " << product.name << " " << curr_idx << end();
 79   ¦ ¦ ¦ names[product.name] = curr_idx;
 80   ¦ ¦ ¦ curr_idx += size_of(product);
 81   ¦ ¦ }
 82   ¦ ¦ int v = lookup_name(product, r);
 83   ¦ ¦ if (v >= 0) {
 84   ¦ ¦ ¦ product.set_value(v);
 85   ¦ ¦ ¦ // Done Placing Product(product, inst, caller)
 86   ¦ ¦ }
 87   ¦ ¦ else {
 88   ¦ ¦ ¦ raise << maybe(caller.name) << "can't find a place to store '" << product.name << "'\n" << end();
 89   ¦ ¦ ¦ return;
 90   ¦ ¦ }
 91   ¦ }
 92   }
 93   if (names_used && numeric_locations_used)
 94   ¦ raise << maybe(caller.name) << "mixing variable names and numeric addresses\n" << end();
 95 }
 96 
 97 bool is_disqualified(/*mutable*/ reagent& x, const instruction& inst, const string& recipe_name) {
 98   if (!x.type) {
 99   ¦ raise << maybe(recipe_name) << "missing type for '" << x.original_string << "' in '" << to_original_string(inst) << "'\n" << end();
100   ¦ // missing-type Error 1
101   ¦ return true;
102   }
103   if (is_raw(x)) return true;
104   if (is_literal(x)) return true;
105   // End is_disqualified Special-cases
106   if (x.initialized) return true;
107   return false;
108 }
109 
110 bool already_transformed(const reagent& r, const map<string, int>& names) {
111   return contains_key(names, r.name);
112 }
113 
114 int lookup_name(const reagent& r, const recipe_ordinal default_recipe) {
115   return Name[default_recipe][r.name];
116 }
117 
118 type_ordinal skip_addresses(type_tree* type) {
119   while (type && is_compound_type_starting_with(type, "address"))
120   ¦ type = type->right;
121   if (!type) return -1;  // error handled elsewhere
122   if (type->atom) return type->value;
123   const type_tree* base_type = type;
124   // Update base_type in skip_addresses
125   if (base_type->atom)
126   ¦ return base_type->value;
127   assert(base_type->left->atom);
128   return base_type->left->value;
129 }
130 
131 bool is_compound_type_starting_with(const type_tree* type, const string& expected_name) {
132   if (!type) return false;
133   if (type->atom) return false;
134   if (!type->left->atom) return false;
135   return type->left->value == get(Type_ordinal, expected_name);
136 }
137 
138 int find_element_name(const type_ordinal t, const string& name, const string& recipe_name) {
139   const type_info& container = get(Type, t);
140   for (int i = 0;  i < SIZE(container.elements);  ++i)
141   ¦ if (container.elements.at(i).name == name) return i;
142   raise << maybe(recipe_name) << "unknown element '" << name << "' in container '" << get(Type, t).name << "'\n" << end();
143   return -1;
144 }
145 
146 bool is_numeric_location(const reagent& x) {
147   if (is_literal(x)) return false;
148   if (is_raw(x)) return false;
149   if (x.name == "0") return false;  // used for chaining lexical scopes
150   return is_integer(x.name);
151 }
152 
153 bool is_named_location(const reagent& x) {
154   if (is_literal(x)) return false;
155   if (is_raw(x)) return false;
156   if (is_special_name(x.name)) return false;
157   return !is_integer(x.name);
158 }
159 
160 // all names here should either be disqualified or also in bind_special_scenario_names
161 bool is_special_name(const string& s) {
162   if (s == "_") return true;
163   if (s == "0") return true;
164   // End is_special_name Special-cases
165   return false;
166 }
167 
168 :(scenario transform_names_supports_containers)
169 def main [
170   x:point <- merge 34, 35
171   y:num <- copy 3
172 ]
173 +name: assign x 1
174 # skip location 2 because x occupies two locations
175 +name: assign y 3
176 
177 :(scenario transform_names_supports_static_arrays)
178 def main [
179   x:@:num:3 <- create-array
180   y:num <- copy 3
181 ]
182 +name: assign x 1
183 # skip locations 2, 3, 4 because x occupies four locations
184 +name: assign y 5
185 
186 :(scenario transform_names_passes_dummy)
187 # _ is just a dummy result that never gets consumed
188 def main [
189   _, x:num <- copy 0, 1
190 ]
191 +name: assign x 1
192 -name: assign _ 1
193 
194 //: an escape hatch to suppress name conversion that we'll use later
195 :(scenarios run)
196 :(scenario transform_names_passes_raw)
197 % Hide_errors = true;
198 def main [
199   x:num/raw <- copy 0
200 ]
201 -name: assign x 1
202 +error: can't write to location 0 in 'x:num/raw <- copy 0'
203 
204 :(scenarios transform)
205 :(scenario transform_names_fails_when_mixing_names_and_numeric_locations)
206 % Hide_errors = true;
207 def main [
208   x:num <- copy 1:num
209 ]
210 +error: main: mixing variable names and numeric addresses
211 
212 :(scenario transform_names_fails_when_mixing_names_and_numeric_locations_2)
213 % Hide_errors = true;
214 def main [
215   x:num <- copy 1
216   1:num <- copy x:num
217 ]
218 +error: main: mixing variable names and numeric addresses
219 
220 :(scenario transform_names_does_not_fail_when_mixing_names_and_raw_locations)
221 def main [
222   x:num <- copy 1:num/raw
223 ]
224 -error: main: mixing variable names and numeric addresses
225 $error: 0
226 
227 :(scenario transform_names_does_not_fail_when_mixing_names_and_literals)
228 def main [
229   x:num <- copy 1
230 ]
231 -error: main: mixing variable names and numeric addresses
232 $error: 0
233 
234 //:: Support element names for containers in 'get' and 'get-location' and 'put'.
235 //: (get-location is implemented later)
236 
237 :(before "End update GET offset_value in Check")
238 else {
239   if (!offset.initialized) {
240   ¦ raise << maybe(get(Recipe, r).name) << "uninitialized offset '" << offset.name << "' in '" << to_original_string(inst) << "'\n" << end();
241   ¦ break;
242   }
243   offset_value = offset.value;
244 }
245 
246 :(scenario transform_names_transforms_container_elements)
247 def main [
248   p:&:point <- copy 0
249   a:num <- get *p:&:point, y:offset
250   b:num <- get *p:&:point, x:offset
251 ]
252 +name: element y of type point is at offset 1
253 +name: element x of type point is at offset 0
254 
255 :(before "End transform_names(inst) Special-cases")
256 // replace element names of containers with offsets
257 if (inst.name == "get" || inst.name == "get-location" || inst.name == "put") {
258   //: avoid raising any errors here; later layers will support overloading new
259   //: instructions with the same names (static dispatch), which could lead to
260   //: spurious errors
261   if (SIZE(inst.ingredients) < 2)
262   ¦ break;  // error raised elsewhere
263   if (!is_literal(inst.ingredients.at(1)))
264   ¦ break;  // error raised elsewhere
265   if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) {
266   ¦ // since first non-address in base type must be a container, we don't have to canonize
267   ¦ type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type);
268   ¦ if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
269   ¦ ¦ inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
270   ¦ ¦ trace(9993, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end();
271   ¦ }
272   }
273 }
274 
275 :(scenario missing_type_in_get)
276 % Hide_errors = true;
277 def main [
278   get a, x:offset
279 ]
280 +error: main: missing type for 'a' in 'get a, x:offset'
281 
282 //: this test is actually illegal so can't call run
283 :(scenarios transform)
284 :(scenario transform_names_handles_containers)
285 def main [
286   a:point <- copy 0/unsafe
287   b:num <- copy 0/unsafe
288 ]
289 +name: assign a 1
290 +name: assign b 3
291 
292 //:: Support variant names for exclusive containers in 'maybe-convert'.
293 
294 :(scenarios run)
295 :(scenario transform_names_handles_exclusive_containers)
296 def main [
297   12:num <- copy 1
298   13:num <- copy 35
299   14:num <- copy 36
300   20:point, 22:bool <- maybe-convert 12:number-or-point/unsafe, p:variant
301 ]
302 +name: variant p of type number-or-point has tag 1
303 +mem: storing 1 in location 22
304 +mem: storing 35 in location 20
305 +mem: storing 36 in location 21
306 
307 :(before "End transform_names(inst) Special-cases")
308 // convert variant names of exclusive containers
309 if (inst.name == "maybe-convert") {
310   if (SIZE(inst.ingredients) != 2) {
311   ¦ raise << maybe(get(Recipe, r).name) << "exactly 2 ingredients expected in '" << to_original_string(inst) << "'\n" << end();
312   ¦ break;
313   }
314   assert(is_literal(inst.ingredients.at(1)));
315   if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) {
316   ¦ // since first non-address in base type must be an exclusive container, we don't have to canonize
317   ¦ type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type);
318   ¦ if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
319   ¦ ¦ inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
320   ¦ ¦ trace(9993, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end();
321   ¦ }
322   }
323 }
324 
325 :(scenario missing_type_in_maybe_convert)
326 % Hide_errors = true;
327 def main [
328   maybe-convert a, x:variant
329 ]
330 +error: main: missing type for 'a' in 'maybe-convert a, x:variant'