1 //: Exclusive containers contain exactly one of a fixed number of 'variants'
2 //: of different types.
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.
23 void test_copy_exclusive_container() {
25 // Copying exclusive containers copies all their contents, and an extra
26 // location for the tag.
28 " 1:num <- copy 1\n" // 'point' variant
31 " 4:number-or-point <- copy 1:number-or-point/unsafe\n"
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.)
46 for (int i
= 0; i
< SIZE(t
.elements
); ++i
) {
48 tmp
.type
= new type_tree(*type
);
49 int size
= size_of(variant_type(tmp
, i
));
50 if (size
> result
) result
= size
;
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
63 :(before
"End Mu Types Initialization")
64 put(Type_ordinal
, "variant", 0);
67 void test_maybe_convert() {
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"
78 "mem: storing 1 in location 22\n"
80 "mem: storing 35 in location 20\n"
81 "mem: storing 36 in location 21\n"
85 void test_maybe_convert_fail() {
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"
96 "mem: storing 0 in location 21\n"
102 :(before
"End Primitive Recipe Declarations")
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();
113 reagent
/*copy*/ base
= inst
.ingredients
.at(0);
114 // Update MAYBE_CONVERT base in Check
116 raise
<< maybe(caller
.name
) << "first ingredient of 'maybe-convert' should be an exclusive-container, but got '" << base
.original_string
<< "'\n" << end();
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();
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();
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();
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();
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();
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();
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();
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
);
185 trace(Callstack_depth
+1, "mem") << "storing 0 in location " << status
.value
<< end();
186 put(Memory
, status
.value
, 0);
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
) {
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
208 void test_maybe_convert_product_type_mismatch() {
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"
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() {
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"
232 CHECK_TRACE_COUNT("error", 0);
235 //:: Allow exclusive containers to be defined in Mu code.
237 void test_exclusive_container() {
239 "exclusive-container foo [\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
260 void test_exclusive_container_contains_array() {
262 "exclusive-container foo [\n"
266 CHECK_TRACE_COUNT("error", 0);
269 void test_exclusive_container_disallows_dynamic_array_element() {
272 "exclusive-container foo [\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() {
284 "exclusive-container foo [\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"
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() {
306 "exclusive-container foo [\n"
314 " 1:foo <- merge 0/x, 34\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() {
327 "exclusive-container foo [\n"
336 " 2:foo <- merge 1:num, 34\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() {
346 "exclusive-container foo [\n"
350 "exclusive-container bar [\n"
356 " 2:bar <- merge 0/a, 34\n"
357 " 4:foo <- merge 1/y, 2:bar\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
)))
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();
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();
384 const reagent
& variant
= variant_type(container
, ingredient
.value
);
385 trace(102, "transform") << "tag: " << ingredient
.value
<< end();
386 // replace union with its variant
388 state
.data
.push(merge_check_point(variant
, 0));
394 void test_merge_check_container_containing_exclusive_container() {
400 "exclusive-container bar [\n"
405 " 1:foo <- merge 23, 1/y, 34\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() {
423 "exclusive-container bar [\n"
428 " 1:foo <- merge 23, 1/y, 34, 35\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() {
438 "exclusive-container foo [\n"
447 " 1:foo <- merge 1/y, 23, 34\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() {
460 "exclusive-container foo [\n"
469 " 1:foo <- merge 0/x, 23\n"
472 CHECK_TRACE_COUNT("error", 0);
475 void test_merge_check_exclusive_container_containing_container_3() {
478 "exclusive-container foo [\n"
487 " 1:foo <- merge 1/y, 23\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() {
497 "exclusive-container foo [\n"
506 " 1:bar <- merge 23, 24\n"
507 " 3:foo <- merge 1/y, 1:bar\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
);
529 void test_merge_exclusive_container_with_mismatched_sizes() {
535 "exclusive-container bar [\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"
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"