Merge pull request #17 from catseye/inconsistent-initialization
Drop check for inconsistent initialization
Chris Pressey authored 3 years ago
GitHub committed 3 years ago
3 | 3 | 0.18 |
4 | 4 | ---- |
5 | 5 | |
6 | * The "consistent initialization" check inside `if` blocks has | |
7 | been dropped. If a location is initialized inside one block | |
8 | but not the other, it is treated as uninitialized afterwards. | |
6 | 9 | * Syntactically, `goto` may only appear at the end of a block. |
7 | 10 | It need no longer be the final instruction in a routine, |
8 | 11 | as long as the type context is consistent at every exit. |
43 | 43 | An alternative would be `static` pointers, which are currently not possible because |
44 | 44 | pointers must be zero-page, thus `@`, thus uninitialized. |
45 | 45 | |
46 | ### Question "consistent initialization" | |
47 | ||
48 | Question the value of the "consistent initialization" principle for `if` statement analysis. | |
49 | ||
50 | Part of this is the trashes at the end; I think what it should be is that the trashes | |
51 | after the `if` is the union of the trashes in each of the branches; this would obviate the | |
52 | need to `trash` values explicitly, but if you tried to access them afterwards, it would still | |
53 | error. | |
54 | ||
55 | 46 | ### Tail-call optimization |
56 | 47 | |
57 | 48 | If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, |
310 | 310 | ld a, 1 |
311 | 311 | st a, player_died |
312 | 312 | } |
313 | ||
314 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, | |
315 | // but currently the compiler cares a little too much about values that are | |
316 | // initialized in one branch of an `if`, but not the other, but are trashed | |
317 | // at the end of the routine anyway. | |
318 | trash ptr | |
319 | trash y | |
320 | trash v | |
321 | 313 | } |
322 | 314 | } |
323 | 315 | |
354 | 346 | add ptr, pos |
355 | 347 | copy 82, [ptr] + y |
356 | 348 | } |
357 | ||
358 | // FIXME these trashes, strictly speaking, probably shouldn't be needed, | |
359 | // but currently the compiler cares too much about values that are | |
360 | // initialized in one branch of an `if`, but not the other, but trashed | |
361 | // at the end of the routine anyway. | |
362 | trash ptr | |
363 | trash y | |
364 | 349 | } else { |
365 | 350 | copy delta, compare_target |
366 | 351 | st on, c |
410 | 410 | |
411 | 411 | self.analyze_block(routine.block, context) |
412 | 412 | |
413 | trashed = set(context.each_touched()) - set(context.each_meaningful()) | |
414 | ||
413 | 415 | if self.debug: |
414 | 416 | print("at end of routine `{}`:".format(routine.name)) |
415 | 417 | print(context) |
436 | 438 | if set(ex.each_writeable()) != exit_writeable: |
437 | 439 | raise InconsistentExitError("Exit contexts are not consistent") |
438 | 440 | context.update_from(exit_context) |
439 | ||
440 | trashed = set(context.each_touched()) - set(context.each_meaningful()) | |
441 | 441 | |
442 | 442 | # these all apply whether we encountered goto(s) in this routine, or not...: |
443 | 443 | |
800 | 800 | outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful()) |
801 | 801 | outgoing_trashes = incoming_meaningful - outgoing_meaningful |
802 | 802 | |
803 | # TODO may we need to deal with touched separately here too? | |
804 | # probably not; if it wasn't meaningful in the first place, it | |
805 | # doesn't really matter if you modified it or not, coming out. | |
806 | for ref in context1.each_meaningful(): | |
807 | if ref in outgoing_trashes: | |
808 | continue | |
809 | context2.assert_meaningful( | |
810 | ref, exception_class=InconsistentInitializationError, | |
811 | message='initialized in block 1 but not in block 2 of `if {}`'.format(instr.src) | |
812 | ) | |
813 | for ref in context2.each_meaningful(): | |
814 | if ref in outgoing_trashes: | |
815 | continue | |
816 | context1.assert_meaningful( | |
817 | ref, exception_class=InconsistentInitializationError, | |
818 | message='initialized in block 2 but not in block 1 of `if {}`'.format(instr.src) | |
819 | ) | |
820 | ||
821 | 803 | # merge the contexts. |
822 | 804 | |
823 | 805 | # first, the easy case: if one of the contexts has terminated, just use the other one. |
1628 | 1628 | | } |
1629 | 1629 | = ok |
1630 | 1630 | |
1631 | If a location is initialized in one block, is must be initialized in the other as well. | |
1631 | If a location is initialized in one block, it must be initialized in the other as well | |
1632 | in order to be considered to be initialized after the block. If it is not consistent, | |
1633 | it will be considered uninitialized. | |
1632 | 1634 | |
1633 | 1635 | | define foo routine |
1634 | 1636 | | inputs a |
1642 | 1644 | | ld a, 23 |
1643 | 1645 | | } |
1644 | 1646 | | } |
1645 | ? InconsistentInitializationError: x | |
1647 | ? UnmeaningfulOutputError: x | |
1646 | 1648 | |
1647 | 1649 | | define foo routine |
1648 | 1650 | | inputs a |
1656 | 1658 | | ld x, 7 |
1657 | 1659 | | } |
1658 | 1660 | | } |
1659 | ? InconsistentInitializationError: x | |
1661 | ? UnmeaningfulOutputError: x | |
1660 | 1662 | |
1661 | 1663 | | define foo routine |
1662 | 1664 | | inputs a |
1670 | 1672 | | ld x, 7 |
1671 | 1673 | | } |
1672 | 1674 | | } |
1673 | ? InconsistentInitializationError: x | |
1675 | ? UnmeaningfulOutputError: x | |
1676 | ||
1677 | | define foo routine | |
1678 | | inputs a | |
1679 | | trashes a, x, z, n, c | |
1680 | | { | |
1681 | | cmp a, 42 | |
1682 | | if not z { | |
1683 | | ld a, 6 | |
1684 | | } else { | |
1685 | | ld x, 7 | |
1686 | | } | |
1687 | | ld a, x | |
1688 | | } | |
1689 | ? UnmeaningfulReadError: x | |
1690 | ||
1691 | If we don't care if it's uninitialized after the `if`, that's okay then. | |
1692 | ||
1693 | | define foo routine | |
1694 | | inputs a | |
1695 | | trashes a, x, z, n, c | |
1696 | | { | |
1697 | | cmp a, 42 | |
1698 | | if not z { | |
1699 | | ld a, 6 | |
1700 | | } else { | |
1701 | | ld x, 7 | |
1702 | | } | |
1703 | | } | |
1704 | = ok | |
1705 | ||
1706 | Or, if it does get initialized on both branches, that's okay then. | |
1707 | ||
1708 | | define foo routine | |
1709 | | inputs a | |
1710 | | outputs x | |
1711 | | trashes a, z, n, c | |
1712 | | { | |
1713 | | cmp a, 42 | |
1714 | | if not z { | |
1715 | | ld x, 0 | |
1716 | | ld a, 6 | |
1717 | | } else { | |
1718 | | ld x, 7 | |
1719 | | } | |
1720 | | } | |
1721 | = ok | |
1674 | 1722 | |
1675 | 1723 | However, this only pertains to initialization. If a value is already |
1676 | 1724 | initialized, either because it was set previous to the `if`, or is an |
1719 | 1767 | | ld x, 7 |
1720 | 1768 | | } |
1721 | 1769 | | } |
1722 | ? InconsistentInitializationError: x | |
1770 | ? UnmeaningfulOutputError: x | |
1723 | 1771 | |
1724 | 1772 | | define foo routine |
1725 | 1773 | | inputs a |