fix other mandelbrot variants
[mu.git] / archive / 1.vm / 033exclusive_container.cc
blobfc944f8db923c1e35ca3471cb9e8e0e877dd1583
1 //: Exclusive containers contain exactly one of a fixed number of 'variants'
2 //: of different types.
3 //:
4 //: They also implicitly contain a tag describing precisely which variant is
5 //: currently stored in them.
7 :(before "End Mu Types Initialization")
8 //: We'll use this container as a running example, with two number elements.
10 type_ordinal tmp = put(Type_ordinal, "number-or-point", Next_type_ordinal++);
11 get_or_insert(Type, tmp); // initialize
12 get(Type, tmp).kind = EXCLUSIVE_CONTAINER;
13 get(Type, tmp).name = "number-or-point";
14 get(Type, tmp).elements.push_back(reagent("i:number"));
15 get(Type, tmp).elements.push_back(reagent("p:point"));
18 //: Tests in this layer often explicitly set up memory before reading it as a
19 //: container. Don't do this in general. I'm tagging such cases with /unsafe;
20 //: they'll be exceptions to later checks.
22 :(code)
23 void test_copy_exclusive_container() {
24 run(
25 // Copying exclusive containers copies all their contents, and an extra
26 // location for the tag.
27 "def main [\n"
28 " 1:num <- copy 1\n" // 'point' variant
29 " 2:num <- copy 34\n"
30 " 3:num <- copy 35\n"
31 " 4:number-or-point <- copy 1:number-or-point/unsafe\n"
32 "]\n"
34 CHECK_TRACE_CONTENTS(
35 "mem: storing 1 in location 4\n"
36 "mem: storing 34 in location 5\n"
37 "mem: storing 35 in location 6\n"
41 :(before "End size_of(type) Special-cases")
42 if (t.kind == EXCLUSIVE_CONTAINER) {
43 // size of an exclusive container is the size of its largest variant
44 // (So like containers, it can't contain arrays.)
45 int result = 0;
46 for (int i = 0; i < SIZE(t.elements); ++i) {
47 reagent tmp;
48 tmp.type = new type_tree(*type);
49 int size = size_of(variant_type(tmp, i));
50 if (size > result) result = size;
52 // ...+1 for its tag.
53 return result+1;
56 //:: To access variants of an exclusive container, use 'maybe-convert'.
57 //: It always returns an address (so that you can modify it) or null (to
58 //: signal that the conversion failed (because the container contains a
59 //: different variant).
61 //: 'maybe-convert' requires a literal in ingredient 1. We'll use a synonym
62 //: called 'variant'.
63 :(before "End Mu Types Initialization")
64 put(Type_ordinal, "variant", 0);
66 :(code)
67 void test_maybe_convert() {
68 run(
69 "def main [\n"
70 " 12:num <- copy 1\n"
71 " 13:num <- copy 35\n"
72 " 14:num <- copy 36\n"
73 " 20:point, 22:bool <- maybe-convert 12:number-or-point/unsafe, 1:variant\n"
74 "]\n"
76 CHECK_TRACE_CONTENTS(
77 // boolean
78 "mem: storing 1 in location 22\n"
79 // point
80 "mem: storing 35 in location 20\n"
81 "mem: storing 36 in location 21\n"
85 void test_maybe_convert_fail() {
86 run(
87 "def main [\n"
88 " 12:num <- copy 1\n"
89 " 13:num <- copy 35\n"
90 " 14:num <- copy 36\n"
91 " 20:num, 21:bool <- maybe-convert 12:number-or-point/unsafe, 0:variant\n"
92 "]\n"
94 CHECK_TRACE_CONTENTS(
95 // boolean
96 "mem: storing 0 in location 21\n"
97 // number: no write
102 :(before "End Primitive Recipe Declarations")
103 MAYBE_CONVERT,
104 :(before "End Primitive Recipe Numbers")
105 put(Recipe_ordinal, "maybe-convert", MAYBE_CONVERT);
106 :(before "End Primitive Recipe Checks")
107 case MAYBE_CONVERT: {
108 const recipe& caller = get(Recipe, r);
109 if (SIZE(inst.ingredients) != 2) {
110 raise << maybe(caller.name) << "'maybe-convert' expects exactly 2 ingredients in '" << to_original_string(inst) << "'\n" << end();
111 break;
113 reagent/*copy*/ base = inst.ingredients.at(0);
114 // Update MAYBE_CONVERT base in Check
115 if (!base.type) {
116 raise << maybe(caller.name) << "first ingredient of 'maybe-convert' should be an exclusive-container, but got '" << base.original_string << "'\n" << end();
117 break;
119 const type_tree* base_type = base.type;
120 // Update MAYBE_CONVERT base_type in Check
121 if (!base_type->atom || base_type->value == 0 || !contains_key(Type, base_type->value) || get(Type, base_type->value).kind != EXCLUSIVE_CONTAINER) {
122 raise << maybe(caller.name) << "first ingredient of 'maybe-convert' should be an exclusive-container, but got '" << base.original_string << "'\n" << end();
123 break;
125 if (!is_literal(inst.ingredients.at(1))) {
126 raise << maybe(caller.name) << "second ingredient of 'maybe-convert' should have type 'variant', but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
127 break;
129 if (inst.products.empty()) break;
130 if (SIZE(inst.products) != 2) {
131 raise << maybe(caller.name) << "'maybe-convert' expects exactly 2 products in '" << to_original_string(inst) << "'\n" << end();
132 break;
134 reagent/*copy*/ product = inst.products.at(0);
135 // Update MAYBE_CONVERT product in Check
136 reagent& offset = inst.ingredients.at(1);
137 populate_value(offset);
138 if (offset.value >= SIZE(get(Type, base_type->value).elements)) {
139 raise << maybe(caller.name) << "invalid tag " << offset.value << " in '" << to_original_string(inst) << '\n' << end();
140 break;
142 const reagent& variant = variant_type(base, offset.value);
143 if (!types_coercible(product, variant)) {
144 raise << maybe(caller.name) << "'maybe-convert " << base.original_string << ", " << inst.ingredients.at(1).original_string << "' should write to " << to_string(variant.type) << " but '" << product.name << "' has type " << to_string(product.type) << '\n' << end();
145 break;
147 reagent/*copy*/ status = inst.products.at(1);
148 // Update MAYBE_CONVERT status in Check
149 if (!is_mu_boolean(status)) {
150 raise << maybe(get(Recipe, r).name) << "second product yielded by 'maybe-convert' should be a boolean, but tried to write to '" << inst.products.at(1).original_string << "'\n" << end();
151 break;
153 break;
155 :(before "End Primitive Recipe Implementations")
156 case MAYBE_CONVERT: {
157 reagent/*copy*/ base = current_instruction().ingredients.at(0);
158 // Update MAYBE_CONVERT base in Run
159 int base_address = base.value;
160 if (base_address == 0) {
161 raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
162 break;
164 int tag = current_instruction().ingredients.at(1).value;
165 reagent/*copy*/ product = current_instruction().products.at(0);
166 // Update MAYBE_CONVERT product in Run
167 reagent/*copy*/ status = current_instruction().products.at(1);
168 // Update MAYBE_CONVERT status in Run
169 // optimization: directly write results to only update first product when necessary
170 write_products = false;
171 if (tag == static_cast<int>(get_or_insert(Memory, base_address))) {
172 const reagent& variant = variant_type(base, tag);
173 trace(Callstack_depth+1, "mem") << "storing 1 in location " << status.value << end();
174 put(Memory, status.value, 1);
175 if (!is_dummy(product)) {
176 // Write Memory in Successful MAYBE_CONVERT in Run
177 for (int i = 0; i < size_of(variant); ++i) {
178 double val = get_or_insert(Memory, base_address+/*skip tag*/1+i);
179 trace(Callstack_depth+1, "mem") << "storing " << no_scientific(val) << " in location " << product.value+i << end();
180 put(Memory, product.value+i, val);
184 else {
185 trace(Callstack_depth+1, "mem") << "storing 0 in location " << status.value << end();
186 put(Memory, status.value, 0);
188 break;
191 :(code)
192 const reagent variant_type(const reagent& base, int tag) {
193 return variant_type(base.type, tag);
196 const reagent variant_type(const type_tree* type, int tag) {
197 assert(tag >= 0);
198 const type_tree* root_type = type->atom ? type : type->left;
199 assert(contains_key(Type, root_type->value));
200 assert(!get(Type, root_type->value).name.empty());
201 const type_info& info = get(Type, root_type->value);
202 assert(info.kind == EXCLUSIVE_CONTAINER);
203 reagent/*copy*/ element = info.elements.at(tag);
204 // End variant_type Special-cases
205 return element;
208 void test_maybe_convert_product_type_mismatch() {
209 Hide_errors = true;
210 run(
211 "def main [\n"
212 " 12:num <- copy 1\n"
213 " 13:num <- copy 35\n"
214 " 14:num <- copy 36\n"
215 " 20:num, 21:bool <- maybe-convert 12:number-or-point/unsafe, 1:variant\n"
216 "]\n"
218 CHECK_TRACE_CONTENTS(
219 "error: main: 'maybe-convert 12:number-or-point/unsafe, 1:variant' should write to point but '20' has type number\n"
223 void test_maybe_convert_dummy_product() {
224 run(
225 "def main [\n"
226 " 12:num <- copy 1\n"
227 " 13:num <- copy 35\n"
228 " 14:num <- copy 36\n"
229 " _, 21:bool <- maybe-convert 12:number-or-point/unsafe, 1:variant\n"
230 "]\n"
232 CHECK_TRACE_COUNT("error", 0);
235 //:: Allow exclusive containers to be defined in Mu code.
237 void test_exclusive_container() {
238 run(
239 "exclusive-container foo [\n"
240 " x:num\n"
241 " y:num\n"
242 "]\n"
244 CHECK_TRACE_CONTENTS(
245 "parse: --- defining exclusive-container foo\n"
246 "parse: element: {x: \"number\"}\n"
247 "parse: element: {y: \"number\"}\n"
251 :(before "End Command Handlers")
252 else if (command == "exclusive-container") {
253 insert_container(command, EXCLUSIVE_CONTAINER, in);
256 //: arrays are disallowed inside exclusive containers unless their length is
257 //: fixed in advance
259 :(code)
260 void test_exclusive_container_contains_array() {
261 run(
262 "exclusive-container foo [\n"
263 " x:@:num:3\n"
264 "]\n"
266 CHECK_TRACE_COUNT("error", 0);
269 void test_exclusive_container_disallows_dynamic_array_element() {
270 Hide_errors = true;
271 run(
272 "exclusive-container foo [\n"
273 " x:@:num\n"
274 "]\n"
276 CHECK_TRACE_CONTENTS(
277 "error: container 'foo' cannot determine size of element 'x'\n"
281 //:: To construct exclusive containers out of variant types, use 'merge'.
282 void test_lift_to_exclusive_container() {
283 run(
284 "exclusive-container foo [\n"
285 " x:num\n"
286 " y:num\n"
287 "]\n"
288 "def main [\n"
289 " 1:num <- copy 34\n"
290 " 2:foo <- merge 0/x, 1:num\n" // tag must be a literal when merging exclusive containers
291 " 4:foo <- merge 1/y, 1:num\n"
292 "]\n"
294 CHECK_TRACE_CONTENTS(
295 "mem: storing 0 in location 2\n"
296 "mem: storing 34 in location 3\n"
297 "mem: storing 1 in location 4\n"
298 "mem: storing 34 in location 5\n"
302 //: type-checking for 'merge' on exclusive containers
304 void test_merge_handles_exclusive_container() {
305 run(
306 "exclusive-container foo [\n"
307 " x:num\n"
308 " y:bar\n"
309 "]\n"
310 "container bar [\n"
311 " z:num\n"
312 "]\n"
313 "def main [\n"
314 " 1:foo <- merge 0/x, 34\n"
315 "]\n"
317 CHECK_TRACE_CONTENTS(
318 "mem: storing 0 in location 1\n"
319 "mem: storing 34 in location 2\n"
321 CHECK_TRACE_COUNT("error", 0);
324 void test_merge_requires_literal_tag_for_exclusive_container() {
325 Hide_errors = true;
326 run(
327 "exclusive-container foo [\n"
328 " x:num\n"
329 " y:bar\n"
330 "]\n"
331 "container bar [\n"
332 " z:num\n"
333 "]\n"
334 "def main [\n"
335 " 1:num <- copy 0\n"
336 " 2:foo <- merge 1:num, 34\n"
337 "]\n"
339 CHECK_TRACE_CONTENTS(
340 "error: main: ingredient 0 of 'merge' should be a literal, for the tag of exclusive-container 'foo' in '2:foo <- merge 1:num, 34'\n"
344 void test_merge_handles_exclusive_container_inside_exclusive_container() {
345 run(
346 "exclusive-container foo [\n"
347 " x:num\n"
348 " y:bar\n"
349 "]\n"
350 "exclusive-container bar [\n"
351 " a:num\n"
352 " b:num\n"
353 "]\n"
354 "def main [\n"
355 " 1:num <- copy 0\n"
356 " 2:bar <- merge 0/a, 34\n"
357 " 4:foo <- merge 1/y, 2:bar\n"
358 "]\n"
360 CHECK_TRACE_CONTENTS(
361 "mem: storing 0 in location 5\n"
362 "mem: storing 34 in location 6\n"
364 CHECK_TRACE_COUNT("error", 0);
367 :(before "End check_merge_call Special-cases")
368 case EXCLUSIVE_CONTAINER: {
369 assert(state.data.top().container_element_index == 0);
370 trace(102, "transform") << "checking exclusive container " << to_string(container) << " vs ingredient " << ingredient_index << end();
371 // easy case: exact match
372 if (types_strictly_match(container, inst.ingredients.at(ingredient_index)))
373 return;
374 if (!is_literal(ingredients.at(ingredient_index))) {
375 raise << maybe(caller.name) << "ingredient " << ingredient_index << " of 'merge' should be a literal, for the tag of exclusive-container '" << container_info.name << "' in '" << to_original_string(inst) << "'\n" << end();
376 return;
378 reagent/*copy*/ ingredient = ingredients.at(ingredient_index); // unnecessary copy just to keep this function from modifying caller
379 populate_value(ingredient);
380 if (ingredient.value >= SIZE(container_info.elements)) {
381 raise << maybe(caller.name) << "invalid tag at " << ingredient_index << " for '" << container_info.name << "' in '" << to_original_string(inst) << "'\n" << end();
382 return;
384 const reagent& variant = variant_type(container, ingredient.value);
385 trace(102, "transform") << "tag: " << ingredient.value << end();
386 // replace union with its variant
387 state.data.pop();
388 state.data.push(merge_check_point(variant, 0));
389 ++ingredient_index;
390 break;
393 :(code)
394 void test_merge_check_container_containing_exclusive_container() {
395 run(
396 "container foo [\n"
397 " x:num\n"
398 " y:bar\n"
399 "]\n"
400 "exclusive-container bar [\n"
401 " x:num\n"
402 " y:num\n"
403 "]\n"
404 "def main [\n"
405 " 1:foo <- merge 23, 1/y, 34\n"
406 "]\n"
408 CHECK_TRACE_CONTENTS(
409 "mem: storing 23 in location 1\n"
410 "mem: storing 1 in location 2\n"
411 "mem: storing 34 in location 3\n"
413 CHECK_TRACE_COUNT("error", 0);
416 void test_merge_check_container_containing_exclusive_container_2() {
417 Hide_errors = true;
418 run(
419 "container foo [\n"
420 " x:num\n"
421 " y:bar\n"
422 "]\n"
423 "exclusive-container bar [\n"
424 " x:num\n"
425 " y:num\n"
426 "]\n"
427 "def main [\n"
428 " 1:foo <- merge 23, 1/y, 34, 35\n"
429 "]\n"
431 CHECK_TRACE_CONTENTS(
432 "error: main: too many ingredients in '1:foo <- merge 23, 1/y, 34, 35'\n"
436 void test_merge_check_exclusive_container_containing_container() {
437 run(
438 "exclusive-container foo [\n"
439 " x:num\n"
440 " y:bar\n"
441 "]\n"
442 "container bar [\n"
443 " x:num\n"
444 " y:num\n"
445 "]\n"
446 "def main [\n"
447 " 1:foo <- merge 1/y, 23, 34\n"
448 "]\n"
450 CHECK_TRACE_CONTENTS(
451 "mem: storing 1 in location 1\n"
452 "mem: storing 23 in location 2\n"
453 "mem: storing 34 in location 3\n"
455 CHECK_TRACE_COUNT("error", 0);
458 void test_merge_check_exclusive_container_containing_container_2() {
459 run(
460 "exclusive-container foo [\n"
461 " x:num\n"
462 " y:bar\n"
463 "]\n"
464 "container bar [\n"
465 " x:num\n"
466 " y:num\n"
467 "]\n"
468 "def main [\n"
469 " 1:foo <- merge 0/x, 23\n"
470 "]\n"
472 CHECK_TRACE_COUNT("error", 0);
475 void test_merge_check_exclusive_container_containing_container_3() {
476 Hide_errors = true;
477 run(
478 "exclusive-container foo [\n"
479 " x:num\n"
480 " y:bar\n"
481 "]\n"
482 "container bar [\n"
483 " x:num\n"
484 " y:num\n"
485 "]\n"
486 "def main [\n"
487 " 1:foo <- merge 1/y, 23\n"
488 "]\n"
490 CHECK_TRACE_CONTENTS(
491 "error: main: too few ingredients in '1:foo <- merge 1/y, 23'\n"
495 void test_merge_check_exclusive_container_containing_container_4() {
496 run(
497 "exclusive-container foo [\n"
498 " x:num\n"
499 " y:bar\n"
500 "]\n"
501 "container bar [\n"
502 " a:num\n"
503 " b:num\n"
504 "]\n"
505 "def main [\n"
506 " 1:bar <- merge 23, 24\n"
507 " 3:foo <- merge 1/y, 1:bar\n"
508 "]\n"
510 CHECK_TRACE_COUNT("error", 0);
513 //: Since the different variants of an exclusive-container might have
514 //: different sizes, relax the size mismatch check for 'merge' instructions.
515 :(before "End size_mismatch(x) Special-cases")
516 if (current_step_index() < SIZE(Current_routine->steps())
517 && current_instruction().operation == MERGE
518 && !current_instruction().products.empty()
519 && current_instruction().products.at(0).type) {
520 reagent/*copy*/ x = current_instruction().products.at(0);
521 // Update size_mismatch Check for MERGE(x)
522 const type_tree* root_type = x.type->atom ? x.type : x.type->left;
523 assert(root_type->atom);
524 if (get(Type, root_type->value).kind == EXCLUSIVE_CONTAINER)
525 return size_of(x) < SIZE(data);
528 :(code)
529 void test_merge_exclusive_container_with_mismatched_sizes() {
530 run(
531 "container foo [\n"
532 " x:num\n"
533 " y:num\n"
534 "]\n"
535 "exclusive-container bar [\n"
536 " x:num\n"
537 " y:foo\n"
538 "]\n"
539 "def main [\n"
540 " 1:num <- copy 34\n"
541 " 2:num <- copy 35\n"
542 " 3:bar <- merge 0/x, 1:num\n"
543 " 6:bar <- merge 1/foo, 1:num, 2:num\n"
544 "]\n"
546 CHECK_TRACE_CONTENTS(
547 "mem: storing 0 in location 3\n"
548 "mem: storing 34 in location 4\n"
549 // bar is always 3 large so location 5 is skipped
550 "mem: storing 1 in location 6\n"
551 "mem: storing 34 in location 7\n"
552 "mem: storing 35 in location 8\n"