1 //: Spaces help isolate recipes from each other. You can create them at will,
  2 //: and all addresses in arguments are implicitly based on the 'default-space'
  3 //: (unless they have the /raw property)
  4 
  5 //: A space is just an array of any scalar location.
  6 :(before "End Mu Types Initialization")
  7 put(Type_abbreviations, "space", new_type_tree("address:array:location"));
  8 //: Spaces are often called 'scopes' in other languages.
  9 put(Type_abbreviations, "scope", new_type_tree("address:array:location"));
 10 
 11 :(scenario set_default_space)
 12 # if default-space is 10, and if an array of 5 locals lies from location 12 to 16 (inclusive),
 13 # then local 0 is really location 12, local 1 is really location 13, and so on.
 14 def main [
 15   # pretend address:array:location; in practice we'll use new
 16   10:num <- copy 0  # refcount
 17   11:num <- copy 5  # length
 18   default-space:space <- copy 10/unsafe
 19   1:num <- copy 23
 20 ]
 21 +mem: storing 23 in location 13
 22 
 23 :(scenario lookup_sidesteps_default_space)
 24 def main [
 25   # pretend pointer from outside (2000 reserved for refcount)
 26   2001:num <- copy 34
 27   # pretend address:array:location; in practice we'll use new
 28   1000:num <- copy 0  # refcount
 29   1001:num <- copy 5  # length
 30   # actual start of this recipe
 31   default-space:space <- copy 1000/unsafe
 32   1:&:num <- copy 2000/unsafe  # even local variables always contain raw addresses
 33   8:num/raw <- copy *1:&:num
 34 ]
 35 +mem: storing 34 in location 8
 36 
 37 //:: first disable name conversion for 'default-space'
 38 :(scenario convert_names_passes_default_space)
 39 % Hide_errors = true;
 40 def main [
 41   default-space:num, x:num <- copy 0, 1
 42 ]
 43 +name: assign x 1
 44 -name: assign default-space 1
 45 
 46 :(before "End is_disqualified Special-cases")
 47 if (x.name == "default-space")
 48   x.initialized = true;
 49 :(before "End is_special_name Special-cases")
 50 if (s == "default-space") return true;
 51 
 52 //:: now implement space support
 53 :(before "End call Fields")
 54 int default_space;
 55 :(before "End call Constructor")
 56 default_space = 0;
 57 
 58 :(before "End canonize(x) Special-cases")
 59 absolutize(x);
 60 :(code)
 61 void absolutize(reagent& x) {
 62   if (is_raw(x) || is_dummy(x)) return;
 63   if (x.name == "default-space") return;
 64   if (!x.initialized)
 65   ¦ raise << to_original_string(current_instruction()) << ": reagent not initialized: '" << x.original_string << "'\n" << end();
 66   x.set_value(address(x.value, space_base(x)));
 67   x.properties.push_back(pair<string, string_tree*>("raw", NULL));
 68   assert(is_raw(x));
 69 }
 70 
 71 //: hook replaced in a later layer
 72 int space_base(const reagent& x) {
 73   return current_call().default_space ? (current_call().default_space+/*skip refcount*/1) : 0;
 74 }
 75 
 76 int address(int offset, int base) {
 77   assert(offset >= 0);
 78   if (base == 0) return offset;  // raw
 79   int size = get_or_insert(Memory, base);
 80   if (offset >= size) {
 81   ¦ // todo: test
 82   ¦ raise << "location " << offset << " is out of bounds " << size << " at " << base << '\n' << end();
 83   ¦ return 0;
 84   }
 85   return base + /*skip length*/1 + offset;
 86 }
 87 
 88 //:: reads and writes to the 'default-space' variable have special behavior
 89 
 90 :(after "Begin Preprocess write_memory(x, data)")
 91 if (x.name == "default-space") {
 92   if (!scalar(data) || !is_space(x))
 93   ¦ raise << maybe(current_recipe_name()) << "'default-space' should be of type address:array:location, but is " << to_string(x.type) << '\n' << end();
 94   current_call().default_space = data.at(0);
 95   return;
 96 }
 97 :(code)
 98 bool is_space(const reagent& r) {
 99   return is_address_of_array_of_numbers(r);
100 }
101 
102 :(scenario get_default_space)
103 def main [
104   default-space:space <- copy 10/unsafe
105   1:space/raw <- copy default-space:space
106 ]
107 +mem: storing 10 in location 1
108 
109 :(after "Begin Preprocess read_memory(x)")
110 if (x.name == "default-space") {
111   vector<double> result;
112   result.push_back(current_call().default_space);
113   return result;
114 }
115 
116 //:: fix 'get'
117 
118 :(scenario lookup_sidesteps_default_space_in_get)
119 def main [
120   # pretend pointer to container from outside (2000 reserved for refcount)
121   2001:num <- copy 34
122   2002:num <- copy 35
123   # pretend address:array:location; in practice we'll use new
124   1000:num <- copy 0  # refcount
125   1001:num <- copy 5  # length
126   # actual start of this recipe
127   default-space:space <- copy 1000/unsafe
128   1:&:point <- copy 2000/unsafe
129   9:num/raw <- get *1:&:point, 1:offset
130 ]
131 +mem: storing 35 in location 9
132 
133 :(before "Read element" following "case GET:")
134 element.properties.push_back(pair<string, string_tree*>("raw", NULL));
135 
136 //:: fix 'index'
137 
138 :(scenario lookup_sidesteps_default_space_in_index)
139 def main [
140   # pretend pointer to array from outside (2000 reserved for refcount)
141   2001:num <- copy 2  # length
142   2002:num <- copy 34
143   2003:num <- copy 35
144   # pretend address:array:location; in practice we'll use new
145   1000:num <- copy 0  # refcount
146   1001:num <- copy 5  # length
147   # actual start of this recipe
148   default-space:space <- copy 1000/unsafe
149   1:&:@:num <- copy 2000/unsafe
150   9:num/raw <- index *1:&:@:num, 1
151 ]
152 +mem: storing 35 in location 9
153 
154 :(before "Read element" following "case INDEX:")
155 element.properties.push_back(pair<string, string_tree*>("raw", NULL));
156 
157 //:: 'new-default-space' is a convenience operation to automatically deduce
158 //:: the amount of space to allocate in a default space with names
159 
160 :(scenario new_default_space)
161 def main [
162   new-default-space
163   x:num <- copy 0
164   y:num <- copy 3
165 ]
166 # allocate space for x and y, as well as the chaining slot at 0
167 +mem: array length is 3
168 
169 :(before "End is_disqualified Special-cases")
170 if (x.name == "number-of-locals")
171   x.initialized = true;
172 :(before "End is_special_name Special-cases")
173 if (s == "number-of-locals") return true;
174 
175 :(before "End Rewrite Instruction(curr, recipe result)")
176 // rewrite 'new-default-space' to
177 //   ```
178 //   default-space:space <- new location:type, number-of-locals:literal
179 //   ```
180 // where number-of-locals is Name[recipe][""]
181 if (curr.name == "new-default-space") {
182   rewrite_default_space_instruction(curr);
183 }
184 :(after "Begin Preprocess read_memory(x)")
185 if (x.name == "number-of-locals") {
186   vector<double> result;
187   result.push_back(Name[get(Recipe_ordinal, current_recipe_name())][""]);
188   if (result.back() == 0)
189   ¦ raise << "no space allocated for default-space in recipe " << current_recipe_name() << "; are you using names?\n" << end();
190   return result;
191 }
192 :(after "Begin Preprocess write_memory(x, data)")
193 if (x.name == "number-of-locals") {
194   raise << maybe(current_recipe_name()) << "can't write to special name 'number-of-locals'\n" << end();
195   return;
196 }
197 
198 //:: 'local-scope' is like 'new-default-space' except that we'll reclaim the
199 //:: default-space when the routine exits
200 
201 :(scenario local_scope)
202 def main [
203   1:&:@:location <- foo
204   2:&:@:location <- foo
205   3:bool <- equal 1:&, 2:&
206 ]
207 def foo [
208   local-scope
209   x:num <- copy 34
210   return default-space:space
211 ]
212 # both calls to foo should have received the same default-space
213 +mem: storing 1 in location 3
214 
215 :(scenario local_scope_frees_up_addresses)
216 def main [
217   local-scope
218   x:text <- new [abc]
219 ]
220 +mem: clearing x:text
221 
222 :(before "End Rewrite Instruction(curr, recipe result)")
223 if (curr.name == "local-scope") {
224   rewrite_default_space_instruction(curr);
225 }
226 
227 //: todo: do this in a transform, rather than magically in the 'return' instruction
228 :(after "Falling Through End Of Recipe")
229 try_reclaim_locals();
230 :(after "Starting Reply")
231 try_reclaim_locals();
232 
233 :(code)
234 void try_reclaim_locals() {
235   if (!Reclaim_memory) return;
236   // only reclaim routines starting with 'local-scope'
237   const recipe_ordinal r = get(Recipe_ordinal, current_recipe_name());
238   const recipe& exiting_recipe = get(Recipe, r);
239   if (exiting_recipe.steps.empty()) return;
240   const instruction& inst = exiting_recipe.steps.at(0);
241   if (inst.name_before_rewrite != "local-scope") return;
242   // reclaim any local variables unless they're being returned
243   vector<double> zeros;
244   for (int i = /*leave default space for last*/1;  i < SIZE(exiting_recipe.steps);  ++i) {
245   ¦ const instruction& inst = exiting_recipe.steps.at(i);
246   ¦ for (int i = 0;  i < SIZE(inst.products);  ++i) {
247   ¦ ¦ const reagent& product = inst.products.at(i);
248   ¦ ¦ // local variables only
249   ¦ ¦ if (has_property(product, "lookup")) continue;
250   ¦ ¦ if (has_property(product, "raw")) continue;  // tests often want to check such locations after they run
251   ¦ ¦ if (escaping(product)) continue;
252   ¦ ¦ // End Checks For Reclaiming Locals
253   ¦ ¦ trace(9999, "mem") << "clearing " << product.original_string << end();
254   ¦ ¦ zeros.resize(size_of(product));
255   ¦ ¦ write_memory(product, zeros);
256   ¦ }
257   }
258   trace(9999, "mem") << "automatically abandoning " << current_call().default_space << end();
259   abandon(current_call().default_space,
260   ¦ ¦ ¦ ¦ inst.products.at(0).type->right,
261   ¦ ¦ ¦ ¦ /*refcount*/1 + /*array length*/1 + /*number-of-locals*/Name[r][""]);
262 }
263 
264 //: Reclaiming local variables above requires remembering what name an
265 //: instruction had before any rewrites or transforms.
266 :(before "End instruction Fields")
267 string name_before_rewrite;
268 :(before "End instruction Clear")
269 name_before_rewrite.clear();
270 :(before "End next_instruction(curr)")
271 curr->name_before_rewrite = curr->name;
272 
273 :(code)
274 // is this reagent one of the values returned by the current (return) instruction?
275 // is the corresponding ingredient saved in the caller?
276 bool escaping(const reagent& r) {
277   assert(Current_routine);  // run-time only
278   // nothing escapes when you fall through past end of recipe
279   if (current_step_index() >= SIZE(Current_routine->steps())) return false;
280   for (long long i = 0;  i < SIZE(current_instruction().ingredients);  ++i) {
281   ¦ if (r == current_instruction().ingredients.at(i)) {
282   ¦ ¦ if (caller_uses_product(i))
283   ¦ ¦ ¦ return true;
284   ¦ }
285   }
286   return false;
287 }
288 
289 //: since we don't decrement refcounts for escaping values above, make sure we
290 //: don't increment them when the caller saves them either
291 
292 :(before "End should_update_refcounts() Special-cases")
293 if (Writing_products_of_instruction) {
294   const instruction& inst = current_instruction();
295   // should_update_refcounts() Special-cases When Writing Products Of Primitive Instructions
296   if (inst.operation < MAX_PRIMITIVE_RECIPES) return true;
297   if (!contains_key(Recipe, inst.operation)) return true;
298   const recipe& callee = get(Recipe, inst.operation);
299   if (callee.steps.empty()) return true;
300   return callee.steps.at(0).name_before_rewrite != "local-scope";  // callees that call local-scope are already dealt with before return
301 }
302 
303 :(code)
304 bool caller_uses_product(int product_index) {
305   assert(Current_routine);  // run-time only
306   assert(!Current_routine->calls.empty());
307   if (Current_routine->calls.size() == 1) return false;
308   const call& caller = *++Current_routine->calls.begin();
309   const instruction& caller_inst = to_instruction(caller);
310   if (product_index >= SIZE(caller_inst.products)) return false;
311   return !is_dummy(caller_inst.products.at(product_index));
312 }
313 
314 void rewrite_default_space_instruction(instruction& curr) {
315   if (!curr.ingredients.empty())
316   ¦ raise << "'" << to_original_string(curr) << "' can't take any ingredients\n" << end();
317   curr.name = "new";
318   curr.ingredients.push_back(reagent("location:type"));
319   curr.ingredients.push_back(reagent("number-of-locals:literal"));
320   if (!curr.products.empty())
321   ¦ raise << "new-default-space can't take any results\n" << end();
322   curr.products.push_back(reagent("default-space:space"));
323 }
324 
325 :(scenario local_scope_frees_up_addresses_inside_containers)
326 container foo [
327   x:num
328   y:&:num
329 ]
330 def main [
331   local-scope
332   x:&:num <- new number:type
333   y:foo <- merge 34, x:&:num
334   # x and y are both cleared when main returns
335 ]
336 +mem: clearing x:&:num
337 +mem: decrementing refcount of 1006: 2 -> 1
338 +mem: clearing y:foo
339 +mem: decrementing refcount of 1006: 1 -> 0
340 +mem: automatically abandoning 1006
341 
342 :(scenario local_scope_returns_addresses_inside_containers)
343 container foo [
344   x:num
345   y:&:num
346 ]
347 def f [
348   local-scope
349   x:&:num <- new number:type
350   *x:&:num <- copy 12
351   y:foo <- merge 34, x:&:num
352   # since y is 'escaping' f, it should not be cleared
353   return y:foo
354 ]
355 def main [
356   1:foo <- f
357   3:num <- get 1:foo, x:offset
358   4:&:num <- get 1:foo, y:offset
359   5:num <- copy *4:&:num
360   1:foo <- put 1:foo, y:offset, 0
361   4:&:num <- copy 0
362 ]
363 +mem: storing 34 in location 1
364 +mem: storing 1006 in location 2
365 +mem: storing 34 in location 3
366 # refcount of 1:foo shouldn't include any stray ones from f
367 +run: {4: ("address" "number")} <- get {1: "foo"}, {y: "offset"}
368 +mem: incrementing refcount of 1006: 1 -> 2
369 # 1:foo wasn't abandoned/cleared
370 +run: {5: "number"} <- copy {4: ("address" "number"), "lookup": ()}
371 +mem: storing 12 in location 5
372 +run: {1: "foo"} <- put {1: "foo"}, {y: "offset"}, {0: "literal"}
373 +mem: decrementing refcount of 1006: 2 -> 1
374 +run: {4: ("address" "number")} <- copy {0: "literal"}
375 +mem: decrementing refcount of 1006: 1 -> 0
376 +mem: automatically abandoning 1006
377 
378 :(scenario local_scope_claims_return_values_when_not_saved)
379 def f [
380   local-scope
381   x:&:num <- new number:type
382   return x:&:num
383 ]
384 def main [
385   f  # doesn't save result
386 ]
387 # x reclaimed
388 +mem: automatically abandoning 1004
389 # f's local scope reclaimed
390 +mem: automatically abandoning 1000
391 
392 //:: all recipes must set default-space one way or another
393 
394 :(before "End Globals")
395 bool Hide_missing_default_space_errors = true;
396 :(before "End Checks")
397 Transform.push_back(check_default_space);  // idempotent
398 :(code)
399 void check_default_space(const recipe_ordinal r) {
400   if (Hide_missing_default_space_errors) return;  // skip previous core tests; this is only for Mu code
401   const recipe& caller = get(Recipe, r);
402   // End check_default_space Special-cases
403   // assume recipes with only numeric addresses know what they're doing (usually tests)
404   if (!contains_non_special_name(r)) return;
405   trace(9991, "transform") << "--- check that recipe " << caller.name << " sets default-space" << end();
406   if (caller.steps.empty()) return;
407   if (caller.steps.at(0).products.empty()
408   ¦ ¦ || caller.steps.at(0).products.at(0).name != "default-space") {
409   ¦ raise << caller.name << " does not seem to start with 'local-scope' or 'default-space'\n" << end();
410   }
411 }
412 :(after "Load Mu Prelude")
413 Hide_missing_default_space_errors = false;
414 :(after "Test Runs")
415 Hide_missing_default_space_errors = true;
416 :(after "Running Main")
417 Hide_missing_default_space_errors = false;
418 
419 :(code)
420 bool contains_non_special_name(const recipe_ordinal r) {
421   for (map<string, int>::iterator p = Name[r].begin();  p != Name[r].end();  ++p) {
422   ¦ if (p->first.empty()) continue;
423   ¦ if (p->first.find("stash_") == 0) continue;  // generated by rewrite_stashes_to_text (cross-layer)
424   ¦ if (!is_special_name(p->first))
425   ¦ ¦ return true;
426   }
427   return false;
428 }
429 
430 // reagent comparison -- only between reagents in a single recipe
431 bool operator==(const reagent& a, const reagent& b) {
432   if (a.name != b.name) return false;
433   if (property(a, "space") != property(b, "space")) return false;
434   return true;
435 }
436 
437 bool operator<(const reagent& a, const reagent& b) {
438   int aspace = 0, bspace = 0;
439   if (has_property(a, "space")) aspace = to_integer(property(a, "space")->value);
440   if (has_property(b, "space")) bspace = to_integer(property(b, "space")->value);
441   if (aspace != bspace) return aspace < bspace;
442   return a.name < b.name;
443 }