mirror of
https://github.com/KarolS/millfork.git
synced 2024-06-12 22:29:33 +00:00
Initial code upload
This commit is contained in:
parent
537d59744e
commit
48e26a0538
674
LICENSE
Normal file
674
LICENSE
Normal file
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
@ -1,6 +1,8 @@
|
||||||
# Millfork
|
# Millfork
|
||||||
|
|
||||||
A middle-level programming language targeting 6502-based microcomputers.
|
A middle-level programming language targeting 6502-based microcomputers.
|
||||||
|
|
||||||
|
Distributed under GPLv3 (see [LICENSE](LICENSE))
|
||||||
|
|
||||||
**UNDER DEVELOPMENT, NOT FOR PRODUCTION USE**
|
**UNDER DEVELOPMENT, NOT FOR PRODUCTION USE**
|
||||||
|
|
||||||
|
|
35
build.sbt
Normal file
35
build.sbt
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
name := "millfork"
|
||||||
|
|
||||||
|
version := "0.0.1-SNAPSHOT"
|
||||||
|
|
||||||
|
scalaVersion := "2.12.3"
|
||||||
|
|
||||||
|
resolvers += Resolver.mavenLocal
|
||||||
|
|
||||||
|
libraryDependencies += "com.lihaoyi" %% "fastparse" % "1.0.0"
|
||||||
|
|
||||||
|
libraryDependencies += "org.apache.commons" % "commons-configuration2" % "2.2"
|
||||||
|
|
||||||
|
libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.4"
|
||||||
|
|
||||||
|
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.4" % "test"
|
||||||
|
|
||||||
|
// these two not in Maven Central or any other public repo
|
||||||
|
// get them from the following links or just build millfork without tests:
|
||||||
|
// https://github.com/sethm/symon
|
||||||
|
// https://github.com/andrew-hoffman/halfnes/tree/061
|
||||||
|
|
||||||
|
libraryDependencies += "com.loomcom.symon" % "symon" % "1.3.0-SNAPSHOT" % "test"
|
||||||
|
|
||||||
|
libraryDependencies += "com.grapeshot" % "halfnes" % "061" % "test"
|
||||||
|
|
||||||
|
mainClass in Compile := Some("millfork.Main")
|
||||||
|
|
||||||
|
assemblyJarName := "millfork.jar"
|
||||||
|
|
||||||
|
//lazy val root = (project in file(".")).
|
||||||
|
// enablePlugins(BuildInfoPlugin).
|
||||||
|
// settings(
|
||||||
|
// buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion),
|
||||||
|
// buildInfoPackage := "hello"
|
||||||
|
// )
|
7
doc/README.md
Normal file
7
doc/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Documentation
|
||||||
|
|
||||||
|
## Tutorial
|
||||||
|
|
||||||
|
* [Getting started](tutorial/01-getting-started.md)
|
||||||
|
|
||||||
|
* [Basic functions and variables](tutorial/02-functions-variables.md)
|
93
doc/target-platforms.md
Normal file
93
doc/target-platforms.md
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# Target platforms
|
||||||
|
|
||||||
|
Currently, Millfork supports creating disk- or tape-based programs for Commodore and Atari 8-bit computers,
|
||||||
|
but it may be expanded to support other 6502-based platforms in the future.
|
||||||
|
|
||||||
|
## Supported platforms
|
||||||
|
|
||||||
|
The following platforms are currently supported:
|
||||||
|
|
||||||
|
* `c64` – Commodore 64
|
||||||
|
|
||||||
|
* `c16` – Commodore 16
|
||||||
|
|
||||||
|
* `plus4` – Commodore Plus/4
|
||||||
|
|
||||||
|
* `vic20` – Commodore VIC-20 without memory expansion
|
||||||
|
|
||||||
|
* `vic20_3k` – Commodore VIC-20 with 3K memory expansion
|
||||||
|
|
||||||
|
* `vic20_8k` – Commodore VIC-20 with 8K or 16K memory expansion
|
||||||
|
|
||||||
|
* `c128` – Commodore 128 in its native mode
|
||||||
|
|
||||||
|
* `pet` – Commodore PET
|
||||||
|
|
||||||
|
* `a8` – Atari 8-bit computers
|
||||||
|
|
||||||
|
The primary and most tested platform is Commodore 64.
|
||||||
|
|
||||||
|
Currently, all targets assume that the program will be loaded from disk or tape.
|
||||||
|
Cartridge targets are not yet available.
|
||||||
|
|
||||||
|
## Adding a custom platform
|
||||||
|
|
||||||
|
Every platform is defined in an `.ini` file with an appropriate name.
|
||||||
|
|
||||||
|
#### `[compilation]` section
|
||||||
|
|
||||||
|
* `arch` – CPU architecture. It defines which instructions are available. Available values:
|
||||||
|
|
||||||
|
* `nmos`
|
||||||
|
|
||||||
|
* `strict` (= NMOS without illegal instructions)
|
||||||
|
|
||||||
|
* `ricoh` (= NMOS without decimal mode)
|
||||||
|
|
||||||
|
* `strictricoh`
|
||||||
|
|
||||||
|
* `cmos` (= 65C02)
|
||||||
|
|
||||||
|
* `modules` – comma-separated list of modules that will be automatically imported
|
||||||
|
|
||||||
|
* other compilation options (they can be overridden using commandline options):
|
||||||
|
|
||||||
|
* `emit_illegals` – whether the compiler should emit illegal instructions, default `false`
|
||||||
|
|
||||||
|
* `emit_cmos` – whether the compiler should emit CMOS instructions, default is `true` on `cmos` and `false` elsewhere
|
||||||
|
|
||||||
|
* `decimal_mode` – whether the compiler should emit decimal instructions, default is `false` on `ricoh` and `strictricoh` and `true` elsewhere
|
||||||
|
|
||||||
|
* `ro_arrays` – whether the compiler should warn upon array writes, default is `false`
|
||||||
|
|
||||||
|
* `prevent_jmp_indirect_bug` – whether the compiler should try to avoid the indirect JMP bug, default is `false` on `cmos` and `true` elsewhere
|
||||||
|
|
||||||
|
#### `[allocation]` section
|
||||||
|
|
||||||
|
* `main_org` – the address for the `main` function; all the other functions will be placed after it
|
||||||
|
|
||||||
|
* `zp_pointers` – either a list of comma separated zeropage addresses that can be used by the program as zeropage pointers, or `all` for all. Each value should be the address of the first of two free bytes in the zeropage.
|
||||||
|
|
||||||
|
* `himem_style` – not yet supported
|
||||||
|
|
||||||
|
* `himem_start` – the first address used for non-zeropage variables, or `after_code` if the variables should be allocated after the code
|
||||||
|
|
||||||
|
* `himem_end` – the last address available for non-zeropage variables
|
||||||
|
|
||||||
|
#### `[output]` section
|
||||||
|
|
||||||
|
* `style` – not yet supported
|
||||||
|
|
||||||
|
* `format` – output file format; a comma-separated list of tokens:
|
||||||
|
|
||||||
|
* literal byte values
|
||||||
|
|
||||||
|
* `startaddr` – little-endian 16-bit address of the first used byte of the compiled output
|
||||||
|
|
||||||
|
* `endaddr` – little-endian 16-bit address of the last used byte of the compiled output
|
||||||
|
|
||||||
|
* `allocated` – all used bytes
|
||||||
|
|
||||||
|
* `<addr>:<addr>` - inclusive range of bytes
|
||||||
|
|
||||||
|
* `extension` – target file extension, with or without the dot
|
54
doc/tutorial/01-getting-started.md
Normal file
54
doc/tutorial/01-getting-started.md
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# Getting started
|
||||||
|
|
||||||
|
## Hello world example
|
||||||
|
|
||||||
|
Save the following as `hello_world.ml`:
|
||||||
|
|
||||||
|
```
|
||||||
|
import stdio
|
||||||
|
|
||||||
|
array hello_world = "hello world" petscii
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
putstr(hello_world, hello_world.length)
|
||||||
|
while(true){}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile is using the following commandline:
|
||||||
|
|
||||||
|
```
|
||||||
|
java millfork.jar hello_world.ml -o hello_world -t c64 -I path_to_millfork\include
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the output executable (here using the VICE emulator):
|
||||||
|
|
||||||
|
```
|
||||||
|
x64 hello_world.prg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic commandline usage
|
||||||
|
|
||||||
|
The following options are crucial when compiling your sources:
|
||||||
|
|
||||||
|
* `-o FILENAME` – specifies the base name for your output file, an appropriate file extension will be appended (`prg` for Commodore, `xex` for Atari, `asm` for assembly output, `lbl` for label file)
|
||||||
|
|
||||||
|
* `-I DIR;DIR;DIR;...` – specifies the paths to directories with modules to include.
|
||||||
|
|
||||||
|
* `-t PLATFORM` – specifies the target platform (`c64` is the default). Each platform is defined in an `.ini` file in the include directory. For the list of supported platforms, see [Supported platforms](../target-platforms.md)
|
||||||
|
|
||||||
|
You may be also interested in the following:
|
||||||
|
|
||||||
|
* `-O`, `-O2`, `-O3` – enable optimization (various levels)
|
||||||
|
|
||||||
|
* `--detailed-flow` – use more resource-consuming but more precise flow analysis engine for better optimization
|
||||||
|
|
||||||
|
* `-s` – additionally generate assembly output
|
||||||
|
|
||||||
|
* `-g` – additionally generate a label file, in format compatible with VICE emulator
|
||||||
|
|
||||||
|
* `-r PROGRAM` – automatically launch given program after successful compilation
|
||||||
|
|
||||||
|
* `-Wall` – enable all warnings
|
||||||
|
|
||||||
|
* `--help` – list all commandline options
|
16
doc/tutorial/02-functions-variables.md
Normal file
16
doc/tutorial/02-functions-variables.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Functions and variables
|
||||||
|
|
||||||
|
TODO: write all of this
|
||||||
|
|
||||||
|
## Basic types
|
||||||
|
|
||||||
|
## Defining variables
|
||||||
|
|
||||||
|
## Built-in operators
|
||||||
|
|
||||||
|
### Byte operators
|
||||||
|
|
||||||
|
| a | a | a |
|
||||||
|
| -- | -- | -- |
|
||||||
|
| a | a | a |
|
||||||
|
|
11
examples/hello_world/hello_world.mfk
Normal file
11
examples/hello_world/hello_world.mfk
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// compile with
|
||||||
|
// java -jar millfork.jar -I ${PATH}/include -t ${platform} ${PATH}/examples/hello_world/hello_world.mfk
|
||||||
|
|
||||||
|
import stdio
|
||||||
|
|
||||||
|
array hello_world = "hello world" petscii
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
putstr(hello_world, hello_world.length)
|
||||||
|
while(true){}
|
||||||
|
}
|
22
include/a8.ini
Normal file
22
include/a8.ini
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[compilation]
|
||||||
|
arch=strict
|
||||||
|
modules=a8_kernel
|
||||||
|
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
main_org=$2000
|
||||||
|
; TODO
|
||||||
|
zp_pointers=$80,$82,$84,$86,$88,$8a,$8c,$8e,$90,$92,$94,$96,$98,$9a,$9c,$9e,$a0,$a2,$a4
|
||||||
|
;TODO
|
||||||
|
himem_style=per_bank
|
||||||
|
himem_start=after_code
|
||||||
|
;TODO
|
||||||
|
himem_end=$3FFF
|
||||||
|
|
||||||
|
[output]
|
||||||
|
;TODO
|
||||||
|
style=per_bank
|
||||||
|
format=$FF,$FF,$E0,$02,$E1,$02,startaddr,startaddr,endaddr,allocated
|
||||||
|
extension=xex
|
||||||
|
|
||||||
|
|
9
include/a8_kernel.mfk
Normal file
9
include/a8_kernel.mfk
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
asm void putchar(byte a) {
|
||||||
|
tax
|
||||||
|
lda $347
|
||||||
|
pha
|
||||||
|
lda $346
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
rts
|
||||||
|
}
|
20
include/c128.ini
Normal file
20
include/c128.ini
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[compilation]
|
||||||
|
arch=nmos
|
||||||
|
modules=c128_hardware,loader_1c01,c128_kernal
|
||||||
|
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
main_org=$1C0D
|
||||||
|
; TODO
|
||||||
|
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
|
himem_style=per_bank
|
||||||
|
himem_start=after_code
|
||||||
|
; TODO
|
||||||
|
himem_end=$FEFF
|
||||||
|
|
||||||
|
[output]
|
||||||
|
style=per_bank
|
||||||
|
format=startaddr,allocated
|
||||||
|
extension=prg
|
||||||
|
|
||||||
|
|
5
include/c128_hardware.mfk
Normal file
5
include/c128_hardware.mfk
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import c64_vic
|
||||||
|
import c64_sid
|
||||||
|
import c64_cia
|
||||||
|
|
||||||
|
array c64_color_ram [1000] @$D800
|
5
include/c128_kernal.mfk
Normal file
5
include/c128_kernal.mfk
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Routines from Commodore 128 KERNAL ROM
|
||||||
|
|
||||||
|
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
||||||
|
// Input: A = Byte to write.
|
||||||
|
asm void putchar(byte a) @$FFD2 extern
|
60
include/c1531.mfk
Normal file
60
include/c1531.mfk
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// mouse driver for Commodore 1531 mouse on Commodore 64
|
||||||
|
|
||||||
|
import mouse
|
||||||
|
import c64_hardware
|
||||||
|
|
||||||
|
sbyte _c1531_calculate_delta (byte old, byte new) {
|
||||||
|
byte mouse_delta
|
||||||
|
mouse_delta = (new - old)
|
||||||
|
mouse_delta &= $3f
|
||||||
|
if mouse_delta >= $20 {
|
||||||
|
mouse_delta |= $c0
|
||||||
|
}
|
||||||
|
return mouse_delta
|
||||||
|
}
|
||||||
|
|
||||||
|
byte _c1531_handle_x() {
|
||||||
|
static byte _c1531_old_pot_x
|
||||||
|
sbyte mouse_delta
|
||||||
|
byte new_pot_x
|
||||||
|
|
||||||
|
new_pot_x = sid_paddle_x >> 1
|
||||||
|
mouse_delta = _c1531_calculate_delta(_c1531_old_pot_x, new_pot_x)
|
||||||
|
_c1531_old_pot_x = new_pot_x
|
||||||
|
|
||||||
|
mouse_x += mouse_delta
|
||||||
|
mouse_x.hi &= 1
|
||||||
|
|
||||||
|
if mouse_x > 319 {
|
||||||
|
if mouse_delta > 0 {
|
||||||
|
mouse_x = 319
|
||||||
|
} else {
|
||||||
|
mouse_x = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte _c1531_handle_y() {
|
||||||
|
static byte _c1531_old_pot_y
|
||||||
|
byte new_pot_y
|
||||||
|
sbyte mouse_delta
|
||||||
|
|
||||||
|
new_pot_y = sid_paddle_y >> 1
|
||||||
|
mouse_delta = _c1531_calculate_delta(_c1531_old_pot_y, new_pot_y)
|
||||||
|
_c1531_old_pot_y = new_pot_y
|
||||||
|
mouse_y -= mouse_delta
|
||||||
|
if mouse_y > 199 {
|
||||||
|
if mouse_delta > 0 {
|
||||||
|
mouse_y = 0
|
||||||
|
} else {
|
||||||
|
mouse_y = 199
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void c1531_mouse () {
|
||||||
|
|
||||||
|
cia1_pra = ($3f & cia1_pra) | $40
|
||||||
|
_c1531_handle_x()
|
||||||
|
_c1531_handle_y()
|
||||||
|
}
|
19
include/c16.ini
Normal file
19
include/c16.ini
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[compilation]
|
||||||
|
arch=nmos
|
||||||
|
modules=loader_1001,c264_kernal,c264_hardware
|
||||||
|
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
main_org=$100D
|
||||||
|
; TODO
|
||||||
|
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
|
himem_style=per_bank
|
||||||
|
himem_start=after_code
|
||||||
|
himem_end=$3FFF
|
||||||
|
|
||||||
|
[output]
|
||||||
|
style=per_bank
|
||||||
|
format=startaddr,allocated
|
||||||
|
extension=prg
|
||||||
|
|
||||||
|
|
1
include/c264_hardware.mfk
Normal file
1
include/c264_hardware.mfk
Normal file
|
@ -0,0 +1 @@
|
||||||
|
import c16_ted
|
5
include/c264_kernal.mfk
Normal file
5
include/c264_kernal.mfk
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Routines from C16 and Plus/4 KERNAL ROM
|
||||||
|
|
||||||
|
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
||||||
|
// Input: A = Byte to write.
|
||||||
|
asm void putchar(byte a) @$FFD2 extern
|
20
include/c264_ted.mfk
Normal file
20
include/c264_ted.mfk
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
const byte black = 0
|
||||||
|
const byte white = $71
|
||||||
|
const byte red = $22
|
||||||
|
const byte cyan = $43
|
||||||
|
const byte purple = $24
|
||||||
|
const byte green = $35
|
||||||
|
const byte blue = $16
|
||||||
|
const byte yellow = $57
|
||||||
|
const byte orange = $28
|
||||||
|
const byte brown = $19
|
||||||
|
const byte light_red = $32
|
||||||
|
const byte dark_grey = $21
|
||||||
|
const byte dark_gray = $21
|
||||||
|
const byte medium_grey = $31
|
||||||
|
const byte medium gray = $31
|
||||||
|
const byte light_green = $55
|
||||||
|
const byte light_blue = $36
|
||||||
|
const byte light_grey = $41
|
||||||
|
const byte light_gray = $41
|
37
include/c64.ini
Normal file
37
include/c64.ini
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
; Commodore 64
|
||||||
|
; assuming a program loaded from disk or tape
|
||||||
|
|
||||||
|
[compilation]
|
||||||
|
; CPU architecture: nmos, strictnmos, ricoh, strictricoh, cmos
|
||||||
|
arch=nmos
|
||||||
|
; modules to load
|
||||||
|
modules=c64_hardware,loader_0801,c64_kernal,stdlib
|
||||||
|
; optionally: default flags
|
||||||
|
emit_illegals=true
|
||||||
|
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
; where the main function should be allocated, also the start of bank 0
|
||||||
|
main_org=$80D
|
||||||
|
; list of free zp pointer locations (these assume that BASIC will keep working)
|
||||||
|
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
|
; where to allocate non-zp variables
|
||||||
|
himem_style=per_bank
|
||||||
|
himem_start=after_code
|
||||||
|
himem_end=$9FFF
|
||||||
|
|
||||||
|
[output]
|
||||||
|
; how the banks are laid out in the output files; so far, there is no bank support in the compiler yet
|
||||||
|
style=per_bank
|
||||||
|
; output file format
|
||||||
|
; startaddr - little-endian address of the first used byte in the bank
|
||||||
|
; endaddr - little-endian address of the last used byte in the bank
|
||||||
|
; allocated - all used bytes in the bank
|
||||||
|
; <addr>:<addr> - bytes from the current bank
|
||||||
|
; <bank>:addr>:<addr> - bytes from arbitrary bank
|
||||||
|
; <byte> - single byte
|
||||||
|
format=startaddr,allocated
|
||||||
|
; default output file extension
|
||||||
|
extension=prg
|
||||||
|
|
||||||
|
|
6
include/c64_basic.mfk
Normal file
6
include/c64_basic.mfk
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// Routines from C64 BASIC ROM
|
||||||
|
|
||||||
|
import c64_kernal
|
||||||
|
|
||||||
|
// print a 16-bit number on the standard output
|
||||||
|
asm void putword(word xa) @$BDCD extern
|
40
include/c64_cia.mfk
Normal file
40
include/c64_cia.mfk
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Hardware addresses for C64
|
||||||
|
|
||||||
|
// CIA1
|
||||||
|
byte cia1_pra @$DC00
|
||||||
|
byte cia1_prb @$DC01
|
||||||
|
byte cia1_ddra @$DC02
|
||||||
|
byte cia1_ddrb @$DC03
|
||||||
|
byte cia2_pra @$DD00
|
||||||
|
byte cia2_prb @$DD01
|
||||||
|
byte cia2_ddra @$DD02
|
||||||
|
byte cia2_ddrb @$DD03
|
||||||
|
|
||||||
|
inline asm void cia_disable_irq() {
|
||||||
|
LDA #$7f
|
||||||
|
LDA $dc0d
|
||||||
|
LDA $dd0d
|
||||||
|
LDA $dc0d
|
||||||
|
LDA $dd0d
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline void vic_bank_0000() {
|
||||||
|
cia2_ddra = $C0
|
||||||
|
cia2_pra = $C0
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_bank_4000() {
|
||||||
|
cia2_ddra = $C0
|
||||||
|
cia2_pra = $80
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_bank_8000() {
|
||||||
|
cia2_ddra = $C0
|
||||||
|
cia2_pra = $40
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_bank_C000() {
|
||||||
|
cia2_ddra = $C0
|
||||||
|
cia2_pra = $00
|
||||||
|
}
|
41
include/c64_hardware.mfk
Normal file
41
include/c64_hardware.mfk
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import c64_vic
|
||||||
|
import c64_sid
|
||||||
|
import c64_cia
|
||||||
|
import cpu6510
|
||||||
|
|
||||||
|
array c64_color_ram [1000] @$D800
|
||||||
|
|
||||||
|
inline void c64_ram_only() {
|
||||||
|
cpu6510_ddr = 7
|
||||||
|
cpu6510_port = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void c64_ram_io() {
|
||||||
|
cpu6510_ddr = 7
|
||||||
|
cpu6510_port = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void c64_ram_io_kernal() {
|
||||||
|
cpu6510_ddr = 7
|
||||||
|
cpu6510_port = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void c64_ram_io_basic() {
|
||||||
|
cpu6510_ddr = 7
|
||||||
|
cpu6510_port = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void c64_ram_charset() {
|
||||||
|
cpu6510_ddr = 7
|
||||||
|
cpu6510_port = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void c64_ram_charset_kernal() {
|
||||||
|
cpu6510_ddr = 7
|
||||||
|
cpu6510_port = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void c64_ram_charset_basic() {
|
||||||
|
cpu6510_ddr = 7
|
||||||
|
cpu6510_port = 3
|
||||||
|
}
|
32
include/c64_kernal.mfk
Normal file
32
include/c64_kernal.mfk
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Routines from C64 KERNAL ROM
|
||||||
|
|
||||||
|
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
||||||
|
// Input: A = Byte to write.
|
||||||
|
asm void putchar(byte a) @$FFD2 extern
|
||||||
|
|
||||||
|
// OPEN. Open file. (Must call SETLFS and SETNAM beforehands.)
|
||||||
|
asm void open() @$FFC0 extern
|
||||||
|
|
||||||
|
// CLOSE. Close file.
|
||||||
|
// Input: A = Logical number.
|
||||||
|
asm void close(byte a) @$FFC0 extern
|
||||||
|
|
||||||
|
// SETLFS. Set file parameters.
|
||||||
|
// Input: A = Logical number; X = Device number; Y = Secondary address.
|
||||||
|
asm void setlfs(byte a, byte x, byte y) @$FFBA extern
|
||||||
|
|
||||||
|
// SETNAM. Set file name parameters.
|
||||||
|
// Input: A = File name length; X/Y = Pointer to file name.
|
||||||
|
asm void setnam(word yx, byte a) @$FFBA extern
|
||||||
|
|
||||||
|
// LOAD. Load or verify file. (Must call SETLFS and SETNAM beforehands.)
|
||||||
|
// Input: A: 0 = Load, 1-255 = Verify; X/Y = Load address (if secondary address = 0).
|
||||||
|
// Output: Carry: 0 = No errors, 1 = Error; A = KERNAL error code (if Carry = 1); X/Y = Address of last byte loaded/verified (if Carry = 0).
|
||||||
|
asm clear_carry load(byte a, word yx) @$FFD5 extern
|
||||||
|
|
||||||
|
// SAVE. Save file. (Must call SETLFS and SETNAM beforehands.)
|
||||||
|
// Input: A = Address of zero page register holding start address of memory area to save; X/Y = End address of memory area plus 1.
|
||||||
|
// Output: Carry: 0 = No errors, 1 = Error; A = KERNAL error code (if Carry = 1).
|
||||||
|
asm clear_carry save(byte a, word yx) @$FFD5 extern
|
||||||
|
|
||||||
|
word irq_pointer @$314
|
24
include/c64_sid.mfk
Normal file
24
include/c64_sid.mfk
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Hardware addresses for C64
|
||||||
|
|
||||||
|
// SID
|
||||||
|
|
||||||
|
word sid_v1_freq @$D400
|
||||||
|
word sid_v1_pulse @$D402
|
||||||
|
byte sid_v1_cr @$D404
|
||||||
|
byte sid_v1_ad @$D405
|
||||||
|
byte sid_v1_sr @$D409
|
||||||
|
|
||||||
|
word sid_v2_freq @$D407
|
||||||
|
word sid_v2_pulse @$D409
|
||||||
|
byte sid_v2_cr @$D40B
|
||||||
|
byte sid_v2_ad @$D40C
|
||||||
|
byte sid_v2_sr @$D40D
|
||||||
|
|
||||||
|
word sid_v3_freq @$D40E
|
||||||
|
word sid_v3_pulse @$D410
|
||||||
|
byte sid_v3_cr @$D412
|
||||||
|
byte sid_v3_ad @$D413
|
||||||
|
byte sid_v3_sr @$D414
|
||||||
|
|
||||||
|
byte sid_paddle_x @$D419
|
||||||
|
byte sid_paddle_y @$D41A
|
154
include/c64_vic.mfk
Normal file
154
include/c64_vic.mfk
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
// Hardware addresses for C64
|
||||||
|
|
||||||
|
// VIC-II
|
||||||
|
byte vic_spr0_x @$D000
|
||||||
|
byte vic_spr0_y @$D001
|
||||||
|
byte vic_spr1_x @$D002
|
||||||
|
byte vic_spr1_y @$D003
|
||||||
|
byte vic_spr2_x @$D004
|
||||||
|
byte vic_spr2_y @$D005
|
||||||
|
byte vic_spr3_x @$D006
|
||||||
|
byte vic_spr3_y @$D007
|
||||||
|
byte vic_spr4_x @$D008
|
||||||
|
byte vic_spr4_y @$D009
|
||||||
|
byte vic_spr5_x @$D00A
|
||||||
|
byte vic_spr5_y @$D00B
|
||||||
|
byte vic_spr6_x @$D00C
|
||||||
|
byte vic_spr6_y @$D00D
|
||||||
|
byte vic_spr7_x @$D00E
|
||||||
|
byte vic_spr7_y @$D00F
|
||||||
|
byte vic_spr_hi_x @$D010
|
||||||
|
byte vic_cr1 @$D011
|
||||||
|
byte vic_raster @$D012
|
||||||
|
byte vic_lp_x @$D013
|
||||||
|
byte vic_lp_y @$D014
|
||||||
|
byte vic_spr_ena @$D015
|
||||||
|
byte vic_cr2 @$D016
|
||||||
|
byte vic_spr_exp_y @$D017
|
||||||
|
byte vic_mem @$D018
|
||||||
|
byte vic_irq @$D019
|
||||||
|
byte vic_irq_ena @$D01A
|
||||||
|
byte vic_spr_dp @$D01B
|
||||||
|
byte vic_spr_mcolor @$D01C
|
||||||
|
byte vic_spr_exp_x @$D01D
|
||||||
|
byte vic_spr_ss_col @$D01E
|
||||||
|
byte vic_spr_sd_col @$D01F
|
||||||
|
byte vic_border @$D020
|
||||||
|
byte vic_bg_color0 @$D021
|
||||||
|
byte vic_bg_color1 @$D022
|
||||||
|
byte vic_bg_color2 @$D023
|
||||||
|
byte vic_bg_color3 @$D024
|
||||||
|
byte vic_spr_color1 @$D025
|
||||||
|
byte vic_spr_color2 @$D026
|
||||||
|
byte vic_spr0_color @$D027
|
||||||
|
byte vic_spr1_color @$D028
|
||||||
|
byte vic_spr2_color @$D029
|
||||||
|
byte vic_spr3_color @$D02A
|
||||||
|
byte vic_spr4_color @$D02B
|
||||||
|
byte vic_spr5_color @$D02C
|
||||||
|
byte vic_spr6_color @$D02D
|
||||||
|
byte vic_spr7_color @$D02E
|
||||||
|
|
||||||
|
array vic_spr_coord [16] @$D000
|
||||||
|
array vic_spr_color [8] @$D027
|
||||||
|
|
||||||
|
inline void vic_enable_multicolor() {
|
||||||
|
vic_cr2 |= 0x10
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_disable_multicolor() {
|
||||||
|
vic_cr2 &= 0xEF
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_enable_bitmap() {
|
||||||
|
vic_cr1 |= 0x20
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_disable_bitmap() {
|
||||||
|
vic_cr1 &= 0xDF
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_24_rows() {
|
||||||
|
vic_cr1 &= 0xF7
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_25_rows() {
|
||||||
|
vic_cr1 |= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_38_columns() {
|
||||||
|
vic_cr2 &= 0xF7
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_40_columns() {
|
||||||
|
vic_cr2 |= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_disable_irq() {
|
||||||
|
vic_irq_ena = 0
|
||||||
|
vic_irq += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// base: divisible by $400, $0000-$3C00 allowed
|
||||||
|
//inline void vic_screen(word const base) {
|
||||||
|
// vic_mem = (vic_mem & $0F) | (base >> 6)
|
||||||
|
//}
|
||||||
|
|
||||||
|
inline void vic_charset_0000() {
|
||||||
|
vic_mem = (vic_mem & $F1)
|
||||||
|
}
|
||||||
|
inline void vic_charset_0800() {
|
||||||
|
vic_mem = (vic_mem & $F1) | 2
|
||||||
|
}
|
||||||
|
inline void vic_charset_1000() {
|
||||||
|
vic_mem = (vic_mem & $F1) | 4
|
||||||
|
}
|
||||||
|
inline void vic_charset_1800() {
|
||||||
|
vic_mem = (vic_mem & $F1) | 6
|
||||||
|
}
|
||||||
|
inline void vic_charset_2000() {
|
||||||
|
vic_mem = (vic_mem & $F1) | 8
|
||||||
|
}
|
||||||
|
inline void vic_charset_2800() {
|
||||||
|
vic_mem = (vic_mem & $F1) | $A
|
||||||
|
}
|
||||||
|
inline void vic_charset_3000() {
|
||||||
|
vic_mem = (vic_mem & $F1) | $C
|
||||||
|
}
|
||||||
|
inline void vic_charset_3800() {
|
||||||
|
vic_mem = (vic_mem & $F1) | $E
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vic_bitmap_0000() {
|
||||||
|
vic_mem &= $F7
|
||||||
|
}
|
||||||
|
inline void vic_bitmap_2000() {
|
||||||
|
vic_mem |= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// x, y < 8
|
||||||
|
// default: x=0, y=3
|
||||||
|
void vic_set_scroll(byte x, byte y) {
|
||||||
|
vic_cr1 = (vic_cr1 & $F8) | y
|
||||||
|
vic_cr2 = (vic_cr2 & $F8) | x
|
||||||
|
}
|
||||||
|
|
||||||
|
const byte black = 0
|
||||||
|
const byte white = 1
|
||||||
|
const byte red = 2
|
||||||
|
const byte cyan = 3
|
||||||
|
const byte purple = 4
|
||||||
|
const byte green = 5
|
||||||
|
const byte blue = 6
|
||||||
|
const byte yellow = 7
|
||||||
|
const byte orange = 8
|
||||||
|
const byte brown = 9
|
||||||
|
const byte light_red = 10
|
||||||
|
const byte dark_grey = 11
|
||||||
|
const byte dark_gray = 11
|
||||||
|
const byte medium_grey = 12
|
||||||
|
const byte medium_gray = 12
|
||||||
|
const byte light_green = 13
|
||||||
|
const byte light_blue = 14
|
||||||
|
const byte light_grey = 15
|
||||||
|
const byte light_gray = 15
|
3
include/cpu6510.mfk
Normal file
3
include/cpu6510.mfk
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
byte cpu6510_ddr @0
|
||||||
|
byte cpu6510_port @1
|
15
include/loader_0401.mfk
Normal file
15
include/loader_0401.mfk
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
array _basic_loader @$401 = [
|
||||||
|
$0b,
|
||||||
|
4,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
$9e,
|
||||||
|
$31,
|
||||||
|
$30,
|
||||||
|
$33,
|
||||||
|
$37,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
|
15
include/loader_0801.mfk
Normal file
15
include/loader_0801.mfk
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
array _basic_loader @$801 = [
|
||||||
|
$0b,
|
||||||
|
$08,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
$9e,
|
||||||
|
$32,
|
||||||
|
$30,
|
||||||
|
$36,
|
||||||
|
$31,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
|
15
include/loader_1001.mfk
Normal file
15
include/loader_1001.mfk
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
array _basic_loader @$1001 = [
|
||||||
|
$0b,
|
||||||
|
$10,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
$9e,
|
||||||
|
$34,
|
||||||
|
$31,
|
||||||
|
$30,
|
||||||
|
$39,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
|
15
include/loader_1201.mfk
Normal file
15
include/loader_1201.mfk
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
array _basic_loader @$1201 = [
|
||||||
|
$0b,
|
||||||
|
$12,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
$9e,
|
||||||
|
$34,
|
||||||
|
$36,
|
||||||
|
$32,
|
||||||
|
$31,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
|
15
include/loader_1c01.mfk
Normal file
15
include/loader_1c01.mfk
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
array _basic_loader @$1C01 = [
|
||||||
|
$0b,
|
||||||
|
$1C,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
$9e,
|
||||||
|
$37,
|
||||||
|
$31,
|
||||||
|
$38,
|
||||||
|
$31,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
|
8
include/mouse.mfk
Normal file
8
include/mouse.mfk
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Generic module for mouse support
|
||||||
|
// Resolutions up to 512x256 are supported
|
||||||
|
|
||||||
|
|
||||||
|
// Mouse X coordinate
|
||||||
|
word mouse_x
|
||||||
|
// Mouse Y coordinate
|
||||||
|
byte mouse_y
|
19
include/pet.ini
Normal file
19
include/pet.ini
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[compilation]
|
||||||
|
arch=nmos
|
||||||
|
modules=loader_0401,pet_kernal
|
||||||
|
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
main_org=$40D
|
||||||
|
; TODO
|
||||||
|
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
|
himem_style=per_bank
|
||||||
|
himem_start=after_code
|
||||||
|
himem_end=$FFF
|
||||||
|
|
||||||
|
[output]
|
||||||
|
style=per_bank
|
||||||
|
format=startaddr,allocated
|
||||||
|
extension=prg
|
||||||
|
|
||||||
|
|
5
include/pet_kernal.mfk
Normal file
5
include/pet_kernal.mfk
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Routines from Commodore PET KERNAL ROM
|
||||||
|
|
||||||
|
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
||||||
|
// Input: A = Byte to write.
|
||||||
|
asm void putchar(byte a) @$FFD2 extern
|
19
include/plus4.ini
Normal file
19
include/plus4.ini
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[compilation]
|
||||||
|
arch=nmos
|
||||||
|
modules=c264_loader,c264_kernal,c264_hardware
|
||||||
|
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
main_org=$100D
|
||||||
|
; TODO
|
||||||
|
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
|
himem_style=per_bank
|
||||||
|
himem_start=after_code
|
||||||
|
himem_end=$7FFF
|
||||||
|
|
||||||
|
[output]
|
||||||
|
style=per_bank
|
||||||
|
format=startaddr,allocated
|
||||||
|
extension=prg
|
||||||
|
|
||||||
|
|
10
include/stdio.mfk
Normal file
10
include/stdio.mfk
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// target-independent standard I/O routines
|
||||||
|
|
||||||
|
void putstr(pointer str, byte len) {
|
||||||
|
byte index
|
||||||
|
index = 0
|
||||||
|
while (index != len) {
|
||||||
|
putchar(str[index])
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
23
include/stdlib.mfk
Normal file
23
include/stdlib.mfk
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// target-independent things
|
||||||
|
|
||||||
|
word nmi_routine_addr @$FFFA
|
||||||
|
word reset_routine_addr @$FFFC
|
||||||
|
word irq_routine_addr @$FFFE
|
||||||
|
|
||||||
|
inline asm void poke(word const addr, byte const value) {
|
||||||
|
?LDA #value
|
||||||
|
STA addr
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asm byte peek(word const addr) {
|
||||||
|
LDA addr
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asm void disable_irq() {
|
||||||
|
SEI
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asm void enable_irq() {
|
||||||
|
CLI
|
||||||
|
}
|
||||||
|
|
19
include/vic20.ini
Normal file
19
include/vic20.ini
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[compilation]
|
||||||
|
arch=nmos
|
||||||
|
modules=loader_1001,vic20_kernal
|
||||||
|
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
main_org=$100D
|
||||||
|
; TODO
|
||||||
|
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
|
himem_style=per_bank
|
||||||
|
himem_start=after_code
|
||||||
|
himem_end=$1CFF
|
||||||
|
|
||||||
|
[output]
|
||||||
|
style=per_bank
|
||||||
|
format=startaddr,allocated
|
||||||
|
extension=prg
|
||||||
|
|
||||||
|
|
19
include/vic20_3k.ini
Normal file
19
include/vic20_3k.ini
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[compilation]
|
||||||
|
arch=nmos
|
||||||
|
modules=loader_0401,vic20_kernal
|
||||||
|
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
main_org=$40D
|
||||||
|
; TODO
|
||||||
|
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
|
himem_style=per_bank
|
||||||
|
himem_start=after_code
|
||||||
|
himem_end=$1CFF
|
||||||
|
|
||||||
|
[output]
|
||||||
|
style=per_bank
|
||||||
|
format=startaddr,allocated
|
||||||
|
extension=prg
|
||||||
|
|
||||||
|
|
19
include/vic20_8k.ini
Normal file
19
include/vic20_8k.ini
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[compilation]
|
||||||
|
arch=nmos
|
||||||
|
modules=loader_1201,vic20_kernal
|
||||||
|
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
main_org=$120D
|
||||||
|
; TODO
|
||||||
|
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
|
himem_style=per_bank
|
||||||
|
himem_start=after_code
|
||||||
|
himem_end=$1FFF
|
||||||
|
|
||||||
|
[output]
|
||||||
|
style=per_bank
|
||||||
|
format=startaddr,allocated
|
||||||
|
extension=prg
|
||||||
|
|
||||||
|
|
5
include/vic20_kernal.mfk
Normal file
5
include/vic20_kernal.mfk
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Routines from C16 and Plus/4 KERNAL ROM
|
||||||
|
|
||||||
|
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
||||||
|
// Input: A = Byte to write.
|
||||||
|
asm void putchar(byte a) @$FFD2 extern
|
2
project/assembly.sbt
Normal file
2
project/assembly.sbt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
|
1
project/build.properties
Normal file
1
project/build.properties
Normal file
|
@ -0,0 +1 @@
|
||||||
|
sbt.version = 0.13.16
|
1
project/buildinfo.sbt
Normal file
1
project/buildinfo.sbt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0")
|
0
project/plugins.sbt
Normal file
0
project/plugins.sbt
Normal file
116
src/main/scala/millfork/CompilationOptions.scala
Normal file
116
src/main/scala/millfork/CompilationOptions.scala
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package millfork
|
||||||
|
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
//
|
||||||
|
//object CompilationOptions {
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// private var instance = new CompilationOptions(Platform.C64, Map())
|
||||||
|
//
|
||||||
|
// // TODO: ugly!
|
||||||
|
// def change(o: CompilationOptions): Unit = {
|
||||||
|
// instance = o
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// def current: CompilationOptions= instance
|
||||||
|
//
|
||||||
|
// def platform: Platform = instance.platform
|
||||||
|
//
|
||||||
|
// def flag(flag: CompilationFlag.Value):Boolean = instance.flags(flag)
|
||||||
|
//
|
||||||
|
// def flags: Map[CompilationFlag.Value, Boolean] = instance.flags
|
||||||
|
//}
|
||||||
|
class CompilationOptions(val platform: Platform, val commandLineFlags: Map[CompilationFlag.Value, Boolean]) {
|
||||||
|
|
||||||
|
import CompilationFlag._
|
||||||
|
import Cpu._
|
||||||
|
|
||||||
|
val flags: Map[CompilationFlag.Value, Boolean] = CompilationFlag.values.map { f =>
|
||||||
|
f -> commandLineFlags.getOrElse(f, platform.flagOverrides.getOrElse(f, Cpu.defaultFlags(platform.cpu)(f)))
|
||||||
|
}.toMap
|
||||||
|
|
||||||
|
def flag(f: CompilationFlag.Value) = flags(f)
|
||||||
|
|
||||||
|
if (flags(DecimalMode)) {
|
||||||
|
if (platform.cpu == Ricoh || platform.cpu == StrictRicoh) {
|
||||||
|
ErrorReporting.warn("Decimal mode enabled for Ricoh architecture", this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (platform.cpu != Cmos) {
|
||||||
|
if (!flags(PreventJmpIndirectBug)) {
|
||||||
|
ErrorReporting.warn("JMP bug prevention should be enabled for non-CMOS architecture", this)
|
||||||
|
}
|
||||||
|
if (flags(EmitCmosOpcodes)) {
|
||||||
|
ErrorReporting.warn("CMOS opcodes enabled for non-CMOS architecture", this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flags(EmitIllegals)) {
|
||||||
|
if (platform.cpu == Cmos) {
|
||||||
|
ErrorReporting.warn("Illegal opcodes enabled for CMOS architecture", this)
|
||||||
|
}
|
||||||
|
if (platform.cpu == StrictRicoh || platform.cpu == Ricoh) {
|
||||||
|
ErrorReporting.warn("Illegal opcodes enabled for strict architecture", this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Cpu extends Enumeration {
|
||||||
|
|
||||||
|
val Mos, StrictMos, Ricoh, StrictRicoh, Cmos = Value
|
||||||
|
|
||||||
|
import CompilationFlag._
|
||||||
|
|
||||||
|
def defaultFlags(x: Cpu.Value): Set[CompilationFlag.Value] = x match {
|
||||||
|
case StrictMos => Set(DecimalMode, PreventJmpIndirectBug, VariableOverlap)
|
||||||
|
case Mos => Set(DecimalMode, PreventJmpIndirectBug, VariableOverlap)
|
||||||
|
case Ricoh => Set(PreventJmpIndirectBug, VariableOverlap)
|
||||||
|
case StrictRicoh => Set(PreventJmpIndirectBug, VariableOverlap)
|
||||||
|
case Cmos => Set(EmitCmosOpcodes, VariableOverlap)
|
||||||
|
}
|
||||||
|
|
||||||
|
def fromString(name: String): Cpu.Value = name match {
|
||||||
|
case "nmos" => Mos
|
||||||
|
case "6502" => Mos
|
||||||
|
case "6510" => Mos
|
||||||
|
case "strict" => StrictMos
|
||||||
|
case "cmos" => Cmos
|
||||||
|
case "65c02" => Cmos
|
||||||
|
case "ricoh" => Ricoh
|
||||||
|
case "2a03" => Ricoh
|
||||||
|
case "2a07" => Ricoh
|
||||||
|
case "strictricoh" => StrictRicoh
|
||||||
|
case "strict2a03" => StrictRicoh
|
||||||
|
case "strict2a07" => StrictRicoh
|
||||||
|
case _ => ErrorReporting.fatal("Unknown CPU achitecture")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object CompilationFlag extends Enumeration {
|
||||||
|
val
|
||||||
|
// compilation options:
|
||||||
|
EmitIllegals, EmitCmosOpcodes, DecimalMode, ReadOnlyArrays, PreventJmpIndirectBug,
|
||||||
|
// optimization options:
|
||||||
|
DetailedFlowAnalysis, DangerousOptimizations,
|
||||||
|
// memory allocation options
|
||||||
|
VariableOverlap,
|
||||||
|
// warning options
|
||||||
|
ExtraComparisonWarnings,
|
||||||
|
RorWarning,
|
||||||
|
FatalWarnings = Value
|
||||||
|
|
||||||
|
val allWarnings: Set[CompilationFlag.Value] = Set(ExtraComparisonWarnings)
|
||||||
|
|
||||||
|
val fromString = Map(
|
||||||
|
"emit_illegals" -> EmitIllegals,
|
||||||
|
"emit_cmos" -> EmitCmosOpcodes,
|
||||||
|
"decimal_mode" -> DecimalMode,
|
||||||
|
"ro_arrays" -> ReadOnlyArrays,
|
||||||
|
"ror_warn" -> RorWarning,
|
||||||
|
"prevent_jmp_indirect_bug" -> PreventJmpIndirectBug,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
264
src/main/scala/millfork/Main.scala
Normal file
264
src/main/scala/millfork/Main.scala
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
package millfork
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.{Files, Paths}
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
import millfork.assembly.opt.{CmosOptimizations, DangerousOptimizations, SuperOptimizer, UndocumentedOptimizations}
|
||||||
|
import millfork.cli.{CliParser, CliStatus}
|
||||||
|
import millfork.env.Environment
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node.StandardCallGraph
|
||||||
|
import millfork.output.Assembler
|
||||||
|
import millfork.parser.SourceLoadingQueue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
case class Context(inputFileNames: List[String],
|
||||||
|
outputFileName: Option[String] = None,
|
||||||
|
runFileName: Option[String] = None,
|
||||||
|
optimizationLevel: Option[Int] = None,
|
||||||
|
platform: Option[String] = None,
|
||||||
|
outputAssembly: Boolean = false,
|
||||||
|
outputLabels: Boolean = false,
|
||||||
|
includePath: List[String] = Nil,
|
||||||
|
flags: Map[CompilationFlag.Value, Boolean] = Map(),
|
||||||
|
verbosity: Option[Int] = None) {
|
||||||
|
def changeFlag(f: CompilationFlag.Value, b: Boolean): Context = {
|
||||||
|
if (flags.contains(f)) {
|
||||||
|
if (flags(f) != b) {
|
||||||
|
ErrorReporting.error("Conflicting flags")
|
||||||
|
}
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
copy(flags = this.flags + (f -> b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Main {
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: Array[String]): Unit = {
|
||||||
|
if (args.isEmpty) {
|
||||||
|
ErrorReporting.info("For help, use --help")
|
||||||
|
}
|
||||||
|
val (status, c) = parser.parse(Context(Nil), args.toList)
|
||||||
|
status match {
|
||||||
|
case CliStatus.Quit => return
|
||||||
|
case CliStatus.Failed =>
|
||||||
|
ErrorReporting.fatalQuit("Invalid command line")
|
||||||
|
case CliStatus.Ok => ()
|
||||||
|
}
|
||||||
|
ErrorReporting.assertNoErrors("Invalid command line")
|
||||||
|
if (c.inputFileNames.isEmpty) {
|
||||||
|
ErrorReporting.fatalQuit("No input files")
|
||||||
|
}
|
||||||
|
ErrorReporting.verbosity = c.verbosity.getOrElse(0)
|
||||||
|
val optLevel = c.optimizationLevel.getOrElse(0)
|
||||||
|
val platform = Platform.lookupPlatformFile(c.includePath, c.platform.getOrElse {
|
||||||
|
ErrorReporting.info("No platform selected, defaulting to `c64`")
|
||||||
|
"c64"
|
||||||
|
})
|
||||||
|
val options = new CompilationOptions(platform, c.flags)
|
||||||
|
ErrorReporting.debug("Effective flags: " + options.flags)
|
||||||
|
|
||||||
|
val output = c.outputFileName.getOrElse("a")
|
||||||
|
val assOutput = output + ".asm"
|
||||||
|
val labelOutput = output + ".lbl"
|
||||||
|
val prgOutput = if (!output.endsWith(platform.fileExtension)) output + platform.fileExtension else output
|
||||||
|
|
||||||
|
val unoptimized = new SourceLoadingQueue(
|
||||||
|
initialFilenames = c.inputFileNames,
|
||||||
|
includePath = c.includePath,
|
||||||
|
options = options).run()
|
||||||
|
|
||||||
|
val program = if (optLevel > 0) {
|
||||||
|
OptimizationPresets.NodeOpt.foldLeft(unoptimized)((p, opt) => p.applyNodeOptimization(opt))
|
||||||
|
} else {
|
||||||
|
unoptimized
|
||||||
|
}
|
||||||
|
val callGraph = new StandardCallGraph(program)
|
||||||
|
|
||||||
|
val env = new Environment(None, "")
|
||||||
|
env.collectDeclarations(program, options)
|
||||||
|
val extras = List(
|
||||||
|
if (options.flag(CompilationFlag.EmitIllegals)) UndocumentedOptimizations.All else Nil,
|
||||||
|
if (options.flag(CompilationFlag.EmitCmosOpcodes)) CmosOptimizations.All else Nil,
|
||||||
|
if (options.flag(CompilationFlag.DangerousOptimizations)) DangerousOptimizations.All else Nil,
|
||||||
|
).flatten
|
||||||
|
val goodCycle = List.fill(optLevel - 1)(OptimizationPresets.Good ++ extras).flatten
|
||||||
|
val assemblyOptimizations = if (optLevel <= 0) Nil else if (optLevel >= 9) List(SuperOptimizer) else {
|
||||||
|
goodCycle ++ OptimizationPresets.AssOpt ++ extras ++ goodCycle
|
||||||
|
}
|
||||||
|
|
||||||
|
// compile
|
||||||
|
val assembler = new Assembler(env)
|
||||||
|
val result = assembler.assemble(callGraph, assemblyOptimizations, options)
|
||||||
|
ErrorReporting.assertNoErrors("Codegen failed")
|
||||||
|
ErrorReporting.debug(f"Unoptimized code size: ${assembler.unoptimizedCodeSize}%5d B")
|
||||||
|
ErrorReporting.debug(f"Optimized code size: ${assembler.optimizedCodeSize}%5d B")
|
||||||
|
ErrorReporting.debug(f"Gain: ${(100L * (assembler.unoptimizedCodeSize - assembler.optimizedCodeSize) / assembler.unoptimizedCodeSize.toDouble).round}%5d%%")
|
||||||
|
ErrorReporting.debug(f"Initialized arrays: ${assembler.initializedArraysSize}%5d B")
|
||||||
|
|
||||||
|
if (c.outputAssembly) {
|
||||||
|
val path = Paths.get(assOutput)
|
||||||
|
ErrorReporting.debug("Writing assembly to " + path.toAbsolutePath)
|
||||||
|
Files.write(path, result.asm.mkString("\n").getBytes(StandardCharsets.UTF_8))
|
||||||
|
}
|
||||||
|
if (c.outputLabels) {
|
||||||
|
val path = Paths.get(labelOutput)
|
||||||
|
ErrorReporting.debug("Writing labels to " + path.toAbsolutePath)
|
||||||
|
Files.write(path, result.labels.sortWith { (a, b) =>
|
||||||
|
val aLocal = a._1.head == '.'
|
||||||
|
val bLocal = b._1.head == '.'
|
||||||
|
if (aLocal == bLocal) a._1 < b._1
|
||||||
|
else b._1 < a._1
|
||||||
|
}.groupBy(_._2).values.map(_.head).toSeq.sortBy(_._2).map { case (l, a) =>
|
||||||
|
val normalized = l.replace('$', '_').replace('.', '_')
|
||||||
|
s"al ${a.toHexString} .$normalized"
|
||||||
|
}.mkString("\n").getBytes(StandardCharsets.UTF_8))
|
||||||
|
}
|
||||||
|
val path = Paths.get(prgOutput)
|
||||||
|
ErrorReporting.debug("Writing output to " + path.toAbsolutePath)
|
||||||
|
Files.write(path, result.code)
|
||||||
|
c.runFileName.foreach(program =>
|
||||||
|
new ProcessBuilder(program, path.toAbsolutePath.toString).start()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def parser = new CliParser[Context] {
|
||||||
|
|
||||||
|
fluff("Main options:", "")
|
||||||
|
|
||||||
|
parameter("-o", "--out").required().placeholder("<file>").action { (p, c) =>
|
||||||
|
assertNone(c.outputFileName, "Output already defined")
|
||||||
|
c.copy(outputFileName = Some(p))
|
||||||
|
}.description("The output file name, without extension.").onWrongNumber(_ => ErrorReporting.fatalQuit("No output file specified"))
|
||||||
|
|
||||||
|
flag("-s").action { c =>
|
||||||
|
c.copy(outputAssembly = true)
|
||||||
|
}.description("Generate also the assembly output.")
|
||||||
|
|
||||||
|
flag("-g").action { c =>
|
||||||
|
c.copy(outputLabels = true)
|
||||||
|
}.description("Generate also the label file.")
|
||||||
|
|
||||||
|
parameter("-t", "--target").placeholder("<platform>").action { (p, c) =>
|
||||||
|
assertNone(c.platform, "Platform already defined")
|
||||||
|
c.copy(platform = Some(p))
|
||||||
|
}.description("Target platform, any of: c64, c16, plus4, vic20, vic20_3k, vic20_8k, pet, c128, a8.")
|
||||||
|
|
||||||
|
parameter("-I", "--include-dir").repeatable().placeholder("<dir>;<dir>;...").action { (paths, c) =>
|
||||||
|
val n = paths.split(";")
|
||||||
|
c.copy(includePath = c.includePath ++ n)
|
||||||
|
}.description("Include paths for modules.")
|
||||||
|
|
||||||
|
parameter("-r", "--run").placeholder("<program>").action { (p, c) =>
|
||||||
|
assertNone(c.runFileName, "Run program already defined")
|
||||||
|
c.copy(runFileName = Some(p))
|
||||||
|
}.description("Program to run after successful compilation.")
|
||||||
|
|
||||||
|
endOfFlags("--").description("Marks the end of options.")
|
||||||
|
|
||||||
|
fluff("", "Verbosity options:", "")
|
||||||
|
|
||||||
|
flag("-q", "--quiet").action { c =>
|
||||||
|
assertNone(c.verbosity, "Cannot use -v and -q together")
|
||||||
|
c.copy(verbosity = Some(-1))
|
||||||
|
}.description("Supress all messages except for errors.")
|
||||||
|
|
||||||
|
private val verbose = flag("-v", "--verbose").maxCount(3).action { c =>
|
||||||
|
if (c.verbosity.exists(_ < 0)) ErrorReporting.error("Cannot use -v and -q together", None)
|
||||||
|
c.copy(verbosity = Some(1 + c.verbosity.getOrElse(0)))
|
||||||
|
}.description("Increase verbosity.")
|
||||||
|
flag("-vv").repeatable().action(c => verbose.encounter(verbose.encounter(verbose.encounter(c)))).description("Increase verbosity even more.")
|
||||||
|
flag("-vvv").repeatable().action(c => verbose.encounter(verbose.encounter(c))).description("Increase verbosity even more.")
|
||||||
|
|
||||||
|
fluff("", "Code generation options:", "")
|
||||||
|
|
||||||
|
boolean("-fcmos-ops", "-fno-cmos-ops").action { (c, v) =>
|
||||||
|
c.changeFlag(CompilationFlag.EmitCmosOpcodes, v)
|
||||||
|
}.description("Whether should emit CMOS opcodes.")
|
||||||
|
boolean("-fillegals", "-fno-illegals").action { (c, v) =>
|
||||||
|
c.changeFlag(CompilationFlag.EmitIllegals, v)
|
||||||
|
}.description("Whether should emit illegal (undocumented) NMOS opcodes.")
|
||||||
|
boolean("-fjmp-fix", "-fno-jmp-fix").action { (c, v) =>
|
||||||
|
c.changeFlag(CompilationFlag.PreventJmpIndirectBug, v)
|
||||||
|
}.description("Whether should prevent indirect JMP bug on page boundary.")
|
||||||
|
boolean("-fdecimal-mode", "-fno-decimal-mode").action { (c, v) =>
|
||||||
|
c.changeFlag(CompilationFlag.DecimalMode, v)
|
||||||
|
}.description("Whether should decimal mode be available.")
|
||||||
|
boolean("-fvariable-overlap", "-fno-variable-overlap").action { (c, v) =>
|
||||||
|
c.changeFlag(CompilationFlag.VariableOverlap, v)
|
||||||
|
}.description("Whether should variables overlap if their scopes do not intersect.")
|
||||||
|
|
||||||
|
fluff("", "Optimization options:", "")
|
||||||
|
|
||||||
|
|
||||||
|
flag("-O0").action { c =>
|
||||||
|
assertNone(c.optimizationLevel, "Optimization level already defined")
|
||||||
|
c.copy(optimizationLevel = Some(0))
|
||||||
|
}.description("Disable all optimizations.")
|
||||||
|
flag("-O").action { c =>
|
||||||
|
assertNone(c.optimizationLevel, "Optimization level already defined")
|
||||||
|
c.copy(optimizationLevel = Some(1))
|
||||||
|
}.description("Optimize code.")
|
||||||
|
for (i <- 2 to 9) {
|
||||||
|
val f = flag("-O" + i).action { c =>
|
||||||
|
assertNone(c.optimizationLevel, "Optimization level already defined")
|
||||||
|
c.copy(optimizationLevel = Some(i))
|
||||||
|
}.description("Optimize code even more.")
|
||||||
|
if (i > 3) f.hidden()
|
||||||
|
}
|
||||||
|
flag("--detailed-flow").action { c =>
|
||||||
|
c.changeFlag(CompilationFlag.DetailedFlowAnalysis, true)
|
||||||
|
}.description("Use detailed flow analysis (experimental).")
|
||||||
|
flag("--dangerous-optimizations").action { c =>
|
||||||
|
c.changeFlag(CompilationFlag.DangerousOptimizations, true)
|
||||||
|
}.description("Use dangerous optimizations (experimental).")
|
||||||
|
|
||||||
|
fluff("", "Warning options:", "")
|
||||||
|
|
||||||
|
flag("-Wall", "--Wall").action { c =>
|
||||||
|
CompilationFlag.allWarnings.foldLeft(c) { (c, f) => c.changeFlag(f, true) }
|
||||||
|
}.description("Enable extra warnings.")
|
||||||
|
|
||||||
|
flag("-Wfatal", "--Wfatal").action { c =>
|
||||||
|
c.changeFlag(CompilationFlag.FatalWarnings, true)
|
||||||
|
}.description("Treat warnings as errors.")
|
||||||
|
|
||||||
|
fluff("", "Other options:", "")
|
||||||
|
|
||||||
|
flag("--help").action(c => {
|
||||||
|
printHelp(20).foreach(println(_))
|
||||||
|
assumeStatus(CliStatus.Quit)
|
||||||
|
c
|
||||||
|
}).description("Display this message.")
|
||||||
|
|
||||||
|
flag("--version").action(c => {
|
||||||
|
println("millfork version ")
|
||||||
|
assumeStatus(CliStatus.Quit)
|
||||||
|
System.exit(0)
|
||||||
|
c
|
||||||
|
}).description("Print the version and quit.")
|
||||||
|
|
||||||
|
|
||||||
|
default.action { (p, c) =>
|
||||||
|
if (p.startsWith("-")) {
|
||||||
|
ErrorReporting.error(s"Invalid option `$p`", None)
|
||||||
|
c
|
||||||
|
} else {
|
||||||
|
c.copy(inputFileNames = c.inputFileNames :+ p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def assertNone[T](value: Option[T], msg: String): Unit = {
|
||||||
|
if (value.isDefined) {
|
||||||
|
ErrorReporting.error(msg, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
src/main/scala/millfork/OptimizationPresets.scala
Normal file
150
src/main/scala/millfork/OptimizationPresets.scala
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package millfork
|
||||||
|
|
||||||
|
import millfork.assembly.opt._
|
||||||
|
import millfork.node.opt.{UnreachableCode, UnusedFunctions, UnusedGlobalVariables, UnusedLocalVariables}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object OptimizationPresets {
|
||||||
|
val NodeOpt = List(
|
||||||
|
UnreachableCode,
|
||||||
|
UnusedFunctions,
|
||||||
|
UnusedLocalVariables,
|
||||||
|
UnusedGlobalVariables,
|
||||||
|
)
|
||||||
|
val AssOpt: List[AssemblyOptimization] = List[AssemblyOptimization](
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
AlwaysGoodOptimizations.PointlessLoadAfterLoadOrStore,
|
||||||
|
LaterOptimizations.PointessLoadingForShifting,
|
||||||
|
AlwaysGoodOptimizations.SimplifiableBitOpsSequence,
|
||||||
|
AlwaysGoodOptimizations.IdempotentDuplicateRemoval,
|
||||||
|
AlwaysGoodOptimizations.BranchInPlaceRemoval,
|
||||||
|
UnusedLabelRemoval,
|
||||||
|
AlwaysGoodOptimizations.UnconditionalJumpRemoval,
|
||||||
|
UnusedLabelRemoval,
|
||||||
|
AlwaysGoodOptimizations.RearrangeMath,
|
||||||
|
LaterOptimizations.PointlessLoadAfterStore,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
AlwaysGoodOptimizations.PointlessOperationAfterLoad,
|
||||||
|
AlwaysGoodOptimizations.PointlessLoadBeforeTransfer,
|
||||||
|
VariableToRegisterOptimization,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
AlwaysGoodOptimizations.PointlessOperationPairRemoval,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
LaterOptimizations.PointlessLoadAfterStore,
|
||||||
|
AlwaysGoodOptimizations.PointlessOperationAfterLoad,
|
||||||
|
AlwaysGoodOptimizations.IdempotentDuplicateRemoval,
|
||||||
|
AlwaysGoodOptimizations.ConstantIndexPropagation,
|
||||||
|
AlwaysGoodOptimizations.PointlessLoadBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.PoinlessFlagChange,
|
||||||
|
AlwaysGoodOptimizations.FlagFlowAnalysis,
|
||||||
|
AlwaysGoodOptimizations.ConstantFlowAnalysis,
|
||||||
|
AlwaysGoodOptimizations.PointlessMath,
|
||||||
|
VariableToRegisterOptimization,
|
||||||
|
ChangeIndexRegisterOptimizationPreferringX2Y,
|
||||||
|
VariableToRegisterOptimization,
|
||||||
|
ChangeIndexRegisterOptimizationPreferringY2X,
|
||||||
|
VariableToRegisterOptimization,
|
||||||
|
AlwaysGoodOptimizations.ConstantFlowAnalysis,
|
||||||
|
LaterOptimizations.DoubleLoadToDifferentRegisters,
|
||||||
|
LaterOptimizations.DoubleLoadToTheSameRegister,
|
||||||
|
LaterOptimizations.DoubleLoadToDifferentRegisters,
|
||||||
|
LaterOptimizations.DoubleLoadToTheSameRegister,
|
||||||
|
LaterOptimizations.DoubleLoadToDifferentRegisters,
|
||||||
|
LaterOptimizations.DoubleLoadToTheSameRegister,
|
||||||
|
AlwaysGoodOptimizations.PointlessStoreAfterLoad,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
AlwaysGoodOptimizations.IdempotentDuplicateRemoval,
|
||||||
|
AlwaysGoodOptimizations.ConstantIndexPropagation,
|
||||||
|
AlwaysGoodOptimizations.ConstantFlowAnalysis,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfers,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeCompare,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeStore,
|
||||||
|
AlwaysGoodOptimizations.PointlessStashingToIndexOverShortSafeBranch,
|
||||||
|
AlwaysGoodOptimizations.RearrangeMath,
|
||||||
|
AlwaysGoodOptimizations.PointlessStoreAfterLoad,
|
||||||
|
AlwaysGoodOptimizations.PointlessLoadBeforeReturn,
|
||||||
|
LaterOptimizations.PointessLoadingForShifting,
|
||||||
|
AlwaysGoodOptimizations.SimplifiableBitOpsSequence,
|
||||||
|
AlwaysGoodOptimizations.SimplifiableBitOpsSequence,
|
||||||
|
AlwaysGoodOptimizations.SimplifiableBitOpsSequence,
|
||||||
|
AlwaysGoodOptimizations.SimplifiableBitOpsSequence,
|
||||||
|
|
||||||
|
LaterOptimizations.LoadingAfterShifting,
|
||||||
|
AlwaysGoodOptimizations.PointlessStoreAfterLoad,
|
||||||
|
AlwaysGoodOptimizations.PoinlessStoreBeforeStore,
|
||||||
|
LaterOptimizations.PointlessLoadAfterStore,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
|
||||||
|
LaterOptimizations.LoadingAfterShifting,
|
||||||
|
AlwaysGoodOptimizations.PointlessStoreAfterLoad,
|
||||||
|
AlwaysGoodOptimizations.PoinlessStoreBeforeStore,
|
||||||
|
LaterOptimizations.PointlessLoadAfterStore,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
|
||||||
|
LaterOptimizations.LoadingAfterShifting,
|
||||||
|
AlwaysGoodOptimizations.PointlessStoreAfterLoad,
|
||||||
|
AlwaysGoodOptimizations.PoinlessStoreBeforeStore,
|
||||||
|
LaterOptimizations.PointlessLoadAfterStore,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
AlwaysGoodOptimizations.TailCallOptimization,
|
||||||
|
AlwaysGoodOptimizations.UnusedCodeRemoval,
|
||||||
|
AlwaysGoodOptimizations.ReverseFlowAnalysis,
|
||||||
|
AlwaysGoodOptimizations.ModificationOfJustWrittenValue,
|
||||||
|
AlwaysGoodOptimizations.PointlessMathFromFlow,
|
||||||
|
AlwaysGoodOptimizations.PointlessMathFromFlow,
|
||||||
|
AlwaysGoodOptimizations.PointlessMathFromFlow,
|
||||||
|
AlwaysGoodOptimizations.PointlessMathFromFlow,
|
||||||
|
AlwaysGoodOptimizations.PointlessMathFromFlow,
|
||||||
|
AlwaysGoodOptimizations.PointlessMathFromFlow,
|
||||||
|
AlwaysGoodOptimizations.PointlessMathFromFlow,
|
||||||
|
AlwaysGoodOptimizations.PointlessMathFromFlow,
|
||||||
|
AlwaysGoodOptimizations.MathOperationOnTwoIdenticalMemoryOperands,
|
||||||
|
LaterOptimizations.UseZeropageAddressingMode,
|
||||||
|
|
||||||
|
LaterOptimizations.UseXInsteadOfStack,
|
||||||
|
LaterOptimizations.UseYInsteadOfStack,
|
||||||
|
LaterOptimizations.IndexSwitchingOptimization,
|
||||||
|
)
|
||||||
|
|
||||||
|
val Good: List[AssemblyOptimization] = List[AssemblyOptimization](
|
||||||
|
AlwaysGoodOptimizations.Adc0Optimization,
|
||||||
|
AlwaysGoodOptimizations.CarryFlagConversion,
|
||||||
|
DangerousOptimizations.ConstantIndexOffsetPropagation,
|
||||||
|
AlwaysGoodOptimizations.BranchInPlaceRemoval,
|
||||||
|
AlwaysGoodOptimizations.ConstantFlowAnalysis,
|
||||||
|
AlwaysGoodOptimizations.ConstantIndexPropagation,
|
||||||
|
AlwaysGoodOptimizations.FlagFlowAnalysis,
|
||||||
|
AlwaysGoodOptimizations.IdempotentDuplicateRemoval,
|
||||||
|
AlwaysGoodOptimizations.ImpossibleBranchRemoval,
|
||||||
|
AlwaysGoodOptimizations.IndexSequenceOptimization,
|
||||||
|
AlwaysGoodOptimizations.MathOperationOnTwoIdenticalMemoryOperands,
|
||||||
|
AlwaysGoodOptimizations.ModificationOfJustWrittenValue,
|
||||||
|
AlwaysGoodOptimizations.PoinlessFlagChange,
|
||||||
|
AlwaysGoodOptimizations.PointlessLoadAfterLoadOrStore,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
AlwaysGoodOptimizations.PointlessLoadBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.PointlessLoadBeforeTransfer,
|
||||||
|
AlwaysGoodOptimizations.PointlessMath,
|
||||||
|
AlwaysGoodOptimizations.PointlessMathFromFlow,
|
||||||
|
AlwaysGoodOptimizations.PointlessOperationAfterLoad,
|
||||||
|
AlwaysGoodOptimizations.PointlessOperationPairRemoval,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfers,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeCompare,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.PointlessStashingToIndexOverShortSafeBranch,
|
||||||
|
AlwaysGoodOptimizations.PointlessStoreAfterLoad,
|
||||||
|
AlwaysGoodOptimizations.PoinlessStoreBeforeStore,
|
||||||
|
AlwaysGoodOptimizations.RearrangeMath,
|
||||||
|
AlwaysGoodOptimizations.RemoveNops,
|
||||||
|
AlwaysGoodOptimizations.ReverseFlowAnalysis,
|
||||||
|
AlwaysGoodOptimizations.SimplifiableBitOpsSequence,
|
||||||
|
AlwaysGoodOptimizations.SmarterShiftingWords,
|
||||||
|
AlwaysGoodOptimizations.UnconditionalJumpRemoval,
|
||||||
|
UnusedLabelRemoval,
|
||||||
|
AlwaysGoodOptimizations.TailCallOptimization,
|
||||||
|
AlwaysGoodOptimizations.UnusedCodeRemoval,
|
||||||
|
)
|
||||||
|
}
|
115
src/main/scala/millfork/Platform.scala
Normal file
115
src/main/scala/millfork/Platform.scala
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package millfork
|
||||||
|
|
||||||
|
import java.io.{File, StringReader}
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.{Files, Paths}
|
||||||
|
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.output._
|
||||||
|
import org.apache.commons.configuration2.INIConfiguration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Platform(
|
||||||
|
val cpu: Cpu.Value,
|
||||||
|
val flagOverrides: Map[CompilationFlag.Value, Boolean],
|
||||||
|
val startingModules: List[String],
|
||||||
|
val outputPackager: OutputPackager,
|
||||||
|
val allocator: VariableAllocator,
|
||||||
|
val org: Int,
|
||||||
|
val fileExtension: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
object Platform {
|
||||||
|
|
||||||
|
val C64 = new Platform(
|
||||||
|
Cpu.Mos,
|
||||||
|
Map(),
|
||||||
|
List("c64_hardware", "c64_loader"),
|
||||||
|
SequenceOutput(List(StartAddressOutput, AllocatedDataOutput)),
|
||||||
|
new VariableAllocator(
|
||||||
|
List(0xC1, 0xC3, 0xFB, 0xFD, 0x39, 0x3B, 0x3D, 0x43, 0x4B),
|
||||||
|
new AfterCodeByteAllocator(0xA000)
|
||||||
|
),
|
||||||
|
0x80D,
|
||||||
|
".prg"
|
||||||
|
)
|
||||||
|
|
||||||
|
def lookupPlatformFile(includePath: List[String], platformName: String): Platform = {
|
||||||
|
includePath.foreach { dir =>
|
||||||
|
val file = Paths.get(dir, platformName + ".ini").toFile
|
||||||
|
ErrorReporting.debug("Checking " + file)
|
||||||
|
if (file.exists()) {
|
||||||
|
return load(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErrorReporting.fatal(s"Platfom definition `$platformName` not found", None)
|
||||||
|
}
|
||||||
|
|
||||||
|
def load(file: File): Platform = {
|
||||||
|
val conf = new INIConfiguration()
|
||||||
|
val bytes = Files.readAllBytes(file.toPath)
|
||||||
|
conf.read(new StringReader(new String(bytes, StandardCharsets.UTF_8)))
|
||||||
|
|
||||||
|
val cs = conf.getSection("compilation")
|
||||||
|
val cpu = Cpu.fromString(cs.get(classOf[String], "cpu", "strict"))
|
||||||
|
val flagOverrides = CompilationFlag.fromString.flatMap { case (k, f) =>
|
||||||
|
cs.get(classOf[String], k, "").toLowerCase match {
|
||||||
|
case "" => None
|
||||||
|
case "false" | "off" | "0" => Some(f -> false)
|
||||||
|
case "true" | "on" | "1" => Some(f -> true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val startingModules = cs.get(classOf[String], "modules", "").split("[, ]+").filter(_.nonEmpty).toList
|
||||||
|
|
||||||
|
val as = conf.getSection("allocation")
|
||||||
|
val org = as.get(classOf[String], "main_org", "") match {
|
||||||
|
case "" => ErrorReporting.fatal(s"Undefined main_org")
|
||||||
|
case m => parseNumber(m)
|
||||||
|
}
|
||||||
|
val freePointers = as.get(classOf[String], "zp_pointers", "all") match {
|
||||||
|
case "all" => List.tabulate(128)(_ * 2)
|
||||||
|
case xs => xs.split("[, ]+").map(parseNumber).toList
|
||||||
|
}
|
||||||
|
val byteAllocator = (as.get(classOf[String], "himem_start", ""), as.get(classOf[String], "himem_end", "")) match {
|
||||||
|
case ("", _) => ErrorReporting.fatal(s"Undefined himem_start")
|
||||||
|
case (_, "") => ErrorReporting.fatal(s"Undefined himem_end")
|
||||||
|
case ("after_code", end) => new AfterCodeByteAllocator(parseNumber(end) + 1)
|
||||||
|
case (start, end) => new UpwardByteAllocator(parseNumber(start), parseNumber(end) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val os = conf.getSection("output")
|
||||||
|
val outputPackager = SequenceOutput(os.get(classOf[String], "format", "").split("[, ]+").filter(_.nonEmpty).map {
|
||||||
|
case "startaddr" => StartAddressOutput
|
||||||
|
case "endaddr" => EndAddressOutput
|
||||||
|
case "allocated" => AllocatedDataOutput
|
||||||
|
case n => n.split(":").filter(_.nonEmpty) match {
|
||||||
|
case Array(b, s, e) => BankFragmentOutput(parseNumber(b), parseNumber(s), parseNumber(e))
|
||||||
|
case Array(s, e) => CurrentBankFragmentOutput(parseNumber(s), parseNumber(e))
|
||||||
|
case Array(b) => ConstOutput(parseNumber(b).toByte)
|
||||||
|
case x => ErrorReporting.fatal(s"Invalid output format: `$x`")
|
||||||
|
}
|
||||||
|
}.toList)
|
||||||
|
var fileExtension = os.get(classOf[String], "extension", ".bin")
|
||||||
|
|
||||||
|
new Platform(cpu, flagOverrides, startingModules, outputPackager,
|
||||||
|
new VariableAllocator(freePointers, byteAllocator), org,
|
||||||
|
if (fileExtension.startsWith(".")) fileExtension else "." + fileExtension)
|
||||||
|
}
|
||||||
|
|
||||||
|
def parseNumber(s: String): Int = {
|
||||||
|
if (s.startsWith("$")) {
|
||||||
|
Integer.parseInt(s.substring(1), 16)
|
||||||
|
} else if (s.startsWith("0x")) {
|
||||||
|
Integer.parseInt(s.substring(2), 16)
|
||||||
|
} else if (s.startsWith("%")) {
|
||||||
|
Integer.parseInt(s.substring(1), 2)
|
||||||
|
} else if (s.startsWith("0b")) {
|
||||||
|
Integer.parseInt(s.substring(2), 2)
|
||||||
|
} else {
|
||||||
|
s.toInt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/main/scala/millfork/SeparatedList.scala
Normal file
50
src/main/scala/millfork/SeparatedList.scala
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package millfork
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
case class SeparatedList[T, S](head: T, tail: List[(S, T)]) {
|
||||||
|
|
||||||
|
def toPairList(initialSeparator: S) = (initialSeparator -> head) :: tail
|
||||||
|
|
||||||
|
def size: Int = tail.size + 1
|
||||||
|
|
||||||
|
def items: List[T] = head :: tail.map(_._2)
|
||||||
|
|
||||||
|
def separators: List[S] = tail.map(_._1)
|
||||||
|
|
||||||
|
def drop(i: Int): SeparatedList[T, S] = if (i == 0) this else SeparatedList(tail(i - 1)._2, tail.drop(i))
|
||||||
|
|
||||||
|
def take(i: Int): SeparatedList[T, S] = if (i <= 0) ??? else SeparatedList(head, tail.take(i - 1))
|
||||||
|
|
||||||
|
def splitAt(i: Int): (SeparatedList[T, S], S, SeparatedList[T, S]) = {
|
||||||
|
val (a, b) = tail.splitAt(i - 1)
|
||||||
|
(SeparatedList(head, a), b.head._1, SeparatedList(b.head._2, b.tail))
|
||||||
|
}
|
||||||
|
|
||||||
|
def indexOfSeparator(p: S => Boolean): Int = 1 + tail.indexWhere(x => p(x._1))
|
||||||
|
|
||||||
|
def ::(pair: (T, S)) = SeparatedList(pair._1, (pair._2 -> head) :: tail)
|
||||||
|
|
||||||
|
def split(p: S => Boolean): SeparatedList[SeparatedList[T, S], S] = {
|
||||||
|
val i = indexOfSeparator(p)
|
||||||
|
if (i <= 0) SeparatedList(this, Nil)
|
||||||
|
else {
|
||||||
|
val (a, b, c) = splitAt(i)
|
||||||
|
(a, b) :: c.split(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object SeparatedList {
|
||||||
|
def of[T, S](t0: T): SeparatedList[T, S] = SeparatedList[T, S](t0, Nil)
|
||||||
|
|
||||||
|
def of[T, S](t0: T, s1: S, t1: T): SeparatedList[T, S] =
|
||||||
|
SeparatedList(t0, List(s1 -> t1))
|
||||||
|
|
||||||
|
def of[T, S](t0: T, s1: S, t1: T, s2: S, t2: T): SeparatedList[T, S] =
|
||||||
|
SeparatedList(t0, List(s1 -> t1, s2 -> t2))
|
||||||
|
|
||||||
|
def of[T, S](t0: T, s1: S, t1: T, s2: S, t2: T, s3: S, t3: T): SeparatedList[T, S] =
|
||||||
|
SeparatedList(t0, List(s1 -> t1, s2 -> t2, s3 -> t3))
|
||||||
|
}
|
305
src/main/scala/millfork/assembly/AssemblyLine.scala
Normal file
305
src/main/scala/millfork/assembly/AssemblyLine.scala
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
package millfork.assembly
|
||||||
|
|
||||||
|
import java.lang.management.MemoryType
|
||||||
|
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.opt.ReadsA
|
||||||
|
import millfork.compiler.{CompilationContext, MlCompiler}
|
||||||
|
import millfork.env._
|
||||||
|
|
||||||
|
//noinspection TypeAnnotation
|
||||||
|
object OpcodeClasses {
|
||||||
|
|
||||||
|
val ReadsAAlways = Set(
|
||||||
|
ADC, AND, BIT, CMP, EOR, ORA, PHA, SBC, STA, TAX, TAY,
|
||||||
|
SAX, SBX, ANC, DCP
|
||||||
|
)
|
||||||
|
val ReadsAIfImplied = Set(ASL, LSR, ROL, ROR, INC, DEC)
|
||||||
|
val ReadsXAlways = Set(
|
||||||
|
CPX, DEX, INX, STX, TXA, TXS, SBX,
|
||||||
|
PLX,
|
||||||
|
XAA, SAX, AHX, SHX
|
||||||
|
)
|
||||||
|
val ReadsYAlways = Set(CPY, DEY, INY, STY, TYA, PLY, SHY)
|
||||||
|
val ReadsZ = Set(BNE, BEQ, PHP)
|
||||||
|
val ReadsN = Set(BMI, BPL, PHP)
|
||||||
|
val ReadsNOrZ = ReadsZ ++ ReadsN
|
||||||
|
val ReadsV = Set(BVS, BVC, PHP)
|
||||||
|
val ReadsD = Set(PHP, ADC, SBC, RRA, ARR, ISC, DCP) // TODO: ??
|
||||||
|
val ReadsC = Set(
|
||||||
|
PHP, ADC, SBC, BCC, BCS, ROL, ROR,
|
||||||
|
ALR, ARR, ISC, RLA, RRA, SLO, SRE // TODO: ??
|
||||||
|
)
|
||||||
|
val ChangesAAlways = Set(
|
||||||
|
TXA, TYA, PLA,
|
||||||
|
ORA, AND, EOR, ADC, LDA, SBC,
|
||||||
|
SLO, RLA, SRE, RRA, LAX, ISC,
|
||||||
|
XAA, ANC, ALR, ARR, LXA, LAS
|
||||||
|
)
|
||||||
|
val ChangesAIfImplied = Set(ASL, LSR, ROL, ROR, INC, DEC)
|
||||||
|
val ChangesX = Set(
|
||||||
|
DEX, INX, TAX, LDX, TSX,
|
||||||
|
SBX, LAX, LXA, LAS,
|
||||||
|
PLX,
|
||||||
|
)
|
||||||
|
val ChangesY = Set(
|
||||||
|
DEY, INY, TAY, LDY
|
||||||
|
)
|
||||||
|
val ChangesS = Set(
|
||||||
|
PHA, PLA, PHP, PLP, TXS,
|
||||||
|
PHX, PHY, PLX, PLY, TAS, LAS
|
||||||
|
)
|
||||||
|
val ChangesMemoryAlways = Set(
|
||||||
|
STA, STY, STZ,
|
||||||
|
STX, DEC, INC,
|
||||||
|
SAX, DCP, ISC,
|
||||||
|
SLO, RLA, SRE, RRA,
|
||||||
|
AHX, SHY, SHX, TAS, LAS
|
||||||
|
)
|
||||||
|
val ChangesMemoryIfNotImplied = Set(
|
||||||
|
ASL, ROL, LSR, ROR
|
||||||
|
)
|
||||||
|
val ReadsMemoryIfNotImpliedOrImmediate = Set(
|
||||||
|
LDY, CPX, CPY,
|
||||||
|
ORA, AND, EOR, ADC, LDA, CMP, SBC,
|
||||||
|
ASL, ROL, LSR, ROR, LDX, DEC, INC,
|
||||||
|
SLO, RLA, SRE, RRA, LAX, DCP, ISC,
|
||||||
|
LAS,
|
||||||
|
TRB, TSB
|
||||||
|
)
|
||||||
|
val OverwritesA = Set(
|
||||||
|
LDA, PLA, TXA, TYA,
|
||||||
|
LAX, LAS
|
||||||
|
)
|
||||||
|
val OverwritesX = Set(
|
||||||
|
TAX, LDX, TSX, PLX,
|
||||||
|
LAX, LAS
|
||||||
|
)
|
||||||
|
val OverwritesY = Set(
|
||||||
|
TAY, LDY, PLY
|
||||||
|
)
|
||||||
|
val OverwritesC = Set(CLC, SEC, PLP)
|
||||||
|
val OverwritesD = Set(CLD, SED, PLP)
|
||||||
|
val OverwritesI = Set(CLI, SEI, PLP)
|
||||||
|
val OverwritesV = Set(CLV, PLP)
|
||||||
|
val ConcernsAAlways = ReadsAAlways ++ ChangesAAlways
|
||||||
|
val ConcernsAIfImplied = ReadsAIfImplied ++ ChangesAIfImplied
|
||||||
|
val ConcernsXAlways = ReadsXAlways | ChangesX
|
||||||
|
val ConcernsYAlways = ReadsYAlways | ChangesY
|
||||||
|
|
||||||
|
val ConcernsStack = Set(
|
||||||
|
PHA, PLA, PHP, PLP,
|
||||||
|
PHX, PLX, PHY, PLY,
|
||||||
|
TXS, TSX,
|
||||||
|
JSR, RTS, RTI,
|
||||||
|
TAS, LAS,
|
||||||
|
)
|
||||||
|
|
||||||
|
val ChangesNAndZ = Set(
|
||||||
|
ADC, AND, ASL, BIT, CMP, CPX, CPY, DEC, DEX, DEY, EOR, INC, INX, INY, LDA,
|
||||||
|
LDX, LDY, LSR, ORA, PLP, ROL, ROR, SBC, TAX, TAY, TXA, TYA,
|
||||||
|
LAX, SBX, ANC, ALR, ARR, DCP, ISC, RLA, RRA, SLO, SRE, SAX,
|
||||||
|
TSB, TRB // These two do not change N, but lets pretend they do for simplicity
|
||||||
|
)
|
||||||
|
val ChangesC = Set(
|
||||||
|
CLC, SEC, ADC, ASL, CMP, CPX, CPY, LSR, PLP, ROL, ROR, SBC,
|
||||||
|
SBX, ANC, ALR, ARR, DCP, ISC, RLA, RRA, SLO, SRE
|
||||||
|
)
|
||||||
|
val ChangesV = Set(
|
||||||
|
ADC, BIT, PLP, SBC,
|
||||||
|
ARR, ISC, RRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
val SupportsAbsoluteX = Set(
|
||||||
|
ORA, AND, EOR, ADC, CMP, SBC,
|
||||||
|
ASL, ROL, LSR, ROR, DEC, INC,
|
||||||
|
SLO, RLA, SRE, RRA, DCP, ISC,
|
||||||
|
STA, LDA, LDY, STZ, SHY,
|
||||||
|
)
|
||||||
|
|
||||||
|
val SupportsAbsoluteY = Set(
|
||||||
|
ORA, AND, EOR, ADC, CMP, SBC,
|
||||||
|
SLO, RLA, SRE, RRA, DCP, ISC,
|
||||||
|
STA, LDA, LDX,
|
||||||
|
LAX, AHX, SHX, TAS, LAS,
|
||||||
|
)
|
||||||
|
|
||||||
|
val SupportsAbsolute = Set(
|
||||||
|
ORA, AND, EOR, ADC, STA, LDA, CMP, SBC,
|
||||||
|
ASL, ROL, LSR, ROR, STX, LDX, DEC, INC,
|
||||||
|
SLO, RLA, SRE, RRA, SAX, LAX, DCP, ISC,
|
||||||
|
STY, LDY,
|
||||||
|
BIT, JMP, JSR,
|
||||||
|
STZ, TRB, TSB,
|
||||||
|
)
|
||||||
|
|
||||||
|
val SupportsZeroPageIndirect = Set(ORA, AND, EOR, ADC, STA, LDA, CMP, SBC)
|
||||||
|
|
||||||
|
val ShortBranching = Set(BEQ, BNE, BMI, BPL, BVC, BVS, BCC, BCS, BRA)
|
||||||
|
val AllDirectJumps = ShortBranching + JMP
|
||||||
|
val AllLinear = Set(
|
||||||
|
ORA, AND, EOR,
|
||||||
|
ADC, SBC, CMP, CPX, CPY,
|
||||||
|
DEC, DEX, DEY, INC, INX, INY,
|
||||||
|
ASL, ROL, LSR, ROR,
|
||||||
|
LDA, STA, LDX, STX, LDY, STY,
|
||||||
|
TAX, TXA, TAY, TYA, TXS, TSX,
|
||||||
|
PLA, PLP, PHA, PHP,
|
||||||
|
BIT, NOP,
|
||||||
|
CLC, SEC, CLD, SED, CLI, SEI, CLV,
|
||||||
|
STZ, PHX, PHY, PLX, PLY, TSB, TRB,
|
||||||
|
SLO, RLA, SRE, RRA, SAX, LAX, DCP, ISC,
|
||||||
|
ANC, ALR, ARR, XAA, LXA, SBX,
|
||||||
|
DISCARD_AF, DISCARD_XF, DISCARD_YF)
|
||||||
|
|
||||||
|
val NoopDiscardsFlags = Set(DISCARD_AF, DISCARD_XF, DISCARD_YF)
|
||||||
|
val DiscardsV = NoopDiscardsFlags | OverwritesV
|
||||||
|
val DiscardsC = NoopDiscardsFlags | OverwritesC
|
||||||
|
val DiscardsD = OverwritesD
|
||||||
|
val DiscardsI = NoopDiscardsFlags | OverwritesI
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object AssemblyLine {
|
||||||
|
|
||||||
|
def treatment(lines: List[AssemblyLine], state: State.Value): Treatment.Value =
|
||||||
|
lines.map(_.treatment(state)).foldLeft(Treatment.Unchanged)(_ ~ _)
|
||||||
|
|
||||||
|
def label(label: String): AssemblyLine = AssemblyLine.label(Label(label))
|
||||||
|
|
||||||
|
def label(label: Label): AssemblyLine = AssemblyLine(LABEL, AddrMode.DoesNotExist, label.toAddress)
|
||||||
|
|
||||||
|
def discardAF() = AssemblyLine(DISCARD_AF, AddrMode.DoesNotExist, Constant.Zero)
|
||||||
|
|
||||||
|
def discardXF() = AssemblyLine(DISCARD_XF, AddrMode.DoesNotExist, Constant.Zero)
|
||||||
|
|
||||||
|
def discardYF() = AssemblyLine(DISCARD_YF, AddrMode.DoesNotExist, Constant.Zero)
|
||||||
|
|
||||||
|
def immediate(opcode: Opcode.Value, value: Int) = AssemblyLine(opcode, AddrMode.Immediate, NumericConstant(value, 1))
|
||||||
|
|
||||||
|
def immediate(opcode: Opcode.Value, value: Constant) = AssemblyLine(opcode, AddrMode.Immediate, value)
|
||||||
|
|
||||||
|
def implied(opcode: Opcode.Value) = AssemblyLine(opcode, AddrMode.Implied, Constant.Zero)
|
||||||
|
|
||||||
|
def variable(ctx: CompilationContext, opcode: Opcode.Value, variable: Variable, offset: Int = 0): List[AssemblyLine] =
|
||||||
|
variable match {
|
||||||
|
case v@MemoryVariable(_, _, VariableAllocationMethod.Zeropage) =>
|
||||||
|
List(AssemblyLine.zeropage(opcode, v.toAddress + offset))
|
||||||
|
case v@RelativeVariable(_, _, _, true) =>
|
||||||
|
List(AssemblyLine.zeropage(opcode, v.toAddress + offset))
|
||||||
|
case v:VariableInMemory => List(AssemblyLine.absolute(opcode, v.toAddress + offset))
|
||||||
|
case v:StackVariable=> List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(opcode, v.baseOffset + offset + ctx.extraStackOffset))
|
||||||
|
}
|
||||||
|
|
||||||
|
def zeropage(opcode: Opcode.Value, addr: Constant) =
|
||||||
|
AssemblyLine(opcode, AddrMode.ZeroPage, addr)
|
||||||
|
|
||||||
|
def zeropage(opcode: Opcode.Value, thing: ThingInMemory, offset: Int = 0) =
|
||||||
|
AssemblyLine(opcode, AddrMode.ZeroPage, thing.toAddress + offset)
|
||||||
|
|
||||||
|
def absolute(opcode: Opcode.Value, addr: Constant) =
|
||||||
|
AssemblyLine(opcode, AddrMode.Absolute, addr)
|
||||||
|
|
||||||
|
def absolute(opcode: Opcode.Value, thing: ThingInMemory, offset: Int = 0) =
|
||||||
|
AssemblyLine(opcode, AddrMode.Absolute, thing.toAddress + offset)
|
||||||
|
|
||||||
|
def relative(opcode: Opcode.Value, thing: ThingInMemory, offset: Int = 0) =
|
||||||
|
AssemblyLine(opcode, AddrMode.Relative, thing.toAddress + offset)
|
||||||
|
|
||||||
|
def relative(opcode: Opcode.Value, label: String) =
|
||||||
|
AssemblyLine(opcode, AddrMode.Relative, Label(label).toAddress)
|
||||||
|
|
||||||
|
def absoluteY(opcode: Opcode.Value, addr: Constant) =
|
||||||
|
AssemblyLine(opcode, AddrMode.AbsoluteY, addr)
|
||||||
|
|
||||||
|
def absoluteY(opcode: Opcode.Value, thing: ThingInMemory, offset: Int = 0) =
|
||||||
|
AssemblyLine(opcode, AddrMode.AbsoluteY, thing.toAddress + offset)
|
||||||
|
|
||||||
|
def absoluteX(opcode: Opcode.Value, addr: Int) =
|
||||||
|
AssemblyLine(opcode, AddrMode.AbsoluteX, NumericConstant(addr, 2))
|
||||||
|
|
||||||
|
def absoluteX(opcode: Opcode.Value, addr: Constant) =
|
||||||
|
AssemblyLine(opcode, AddrMode.AbsoluteX, addr)
|
||||||
|
|
||||||
|
def absoluteX(opcode: Opcode.Value, thing: ThingInMemory, offset: Int = 0) =
|
||||||
|
AssemblyLine(opcode, AddrMode.AbsoluteX, thing.toAddress + offset)
|
||||||
|
|
||||||
|
def indexedY(opcode: Opcode.Value, addr: Constant) =
|
||||||
|
AssemblyLine(opcode, AddrMode.IndexedY, addr)
|
||||||
|
|
||||||
|
def indexedY(opcode: Opcode.Value, thing: ThingInMemory, offset: Int = 0) =
|
||||||
|
AssemblyLine(opcode, AddrMode.IndexedY, thing.toAddress + offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class AssemblyLine(opcode: Opcode.Value, addrMode: AddrMode.Value, var parameter: Constant, elidable: Boolean = true) {
|
||||||
|
|
||||||
|
|
||||||
|
import AddrMode._
|
||||||
|
import State._
|
||||||
|
import OpcodeClasses._
|
||||||
|
import Treatment._
|
||||||
|
|
||||||
|
def reads(state: State.Value): Boolean = state match {
|
||||||
|
case A => if (addrMode == Implied) ReadsAIfImplied(opcode) else ReadsAAlways(opcode)
|
||||||
|
case X => addrMode == AbsoluteX || addrMode == ZeroPageX || addrMode == IndexedX || ReadsXAlways(opcode)
|
||||||
|
case Y => addrMode == AbsoluteY || addrMode == ZeroPageY || addrMode == IndexedY || ReadsYAlways(opcode)
|
||||||
|
case C => ReadsC(opcode)
|
||||||
|
case D => ReadsD(opcode)
|
||||||
|
case N => ReadsN(opcode)
|
||||||
|
case V => ReadsV(opcode)
|
||||||
|
case Z => ReadsZ(opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
def treatment(state: State.Value): Treatment.Value = opcode match {
|
||||||
|
case LABEL => Unchanged // TODO: ???
|
||||||
|
case NOP => Unchanged
|
||||||
|
case JSR | JMP | BEQ | BNE | BMI | BPL | BRK | BCC | BVC | BCS | BVS => Changed
|
||||||
|
case CLC => if (state == C) Cleared else Unchanged
|
||||||
|
case SEC => if (state == C) Set else Unchanged
|
||||||
|
case CLV => if (state == V) Cleared else Unchanged
|
||||||
|
case CLD => if (state == D) Cleared else Unchanged
|
||||||
|
case SED => if (state == D) Set else Unchanged
|
||||||
|
case _ => state match { // TODO: smart detection of constants
|
||||||
|
case A =>
|
||||||
|
if (ChangesAAlways(opcode) || addrMode == Implied && ChangesAIfImplied(opcode))
|
||||||
|
Changed
|
||||||
|
else
|
||||||
|
Unchanged
|
||||||
|
case X => if (ChangesX(opcode)) Changed else Unchanged
|
||||||
|
case Y => if (ChangesY(opcode)) Changed else Unchanged
|
||||||
|
case C => if (ChangesC(opcode)) Changed else Unchanged
|
||||||
|
case V => if (ChangesV(opcode)) Changed else Unchanged
|
||||||
|
case N | Z => if (ChangesNAndZ(opcode)) Changed else Unchanged
|
||||||
|
case D => Unchanged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def sizeInBytes: Int = addrMode match {
|
||||||
|
case Implied => 1
|
||||||
|
case Relative | ZeroPageX | ZeroPage | ZeroPageY | IndexedX | IndexedY | Immediate => 2
|
||||||
|
case AbsoluteX | Absolute | AbsoluteY | Indirect => 3
|
||||||
|
case DoesNotExist => 0
|
||||||
|
}
|
||||||
|
|
||||||
|
def cost: Int = addrMode match {
|
||||||
|
case Implied => 1000
|
||||||
|
case Relative | Immediate => 2000
|
||||||
|
case ZeroPage => 2001
|
||||||
|
case ZeroPageX | ZeroPageY => 2002
|
||||||
|
case IndexedX | IndexedY => 2003
|
||||||
|
case Absolute => 3000
|
||||||
|
case AbsoluteX | AbsoluteY | Indirect => 3001
|
||||||
|
case DoesNotExist => 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def isPrintable: Boolean = true //addrMode != AddrMode.DoesNotExist || opcode == LABEL
|
||||||
|
|
||||||
|
override def toString: String =
|
||||||
|
if (opcode == LABEL) {
|
||||||
|
parameter.toString
|
||||||
|
} else if (addrMode == DoesNotExist) {
|
||||||
|
s" ; $opcode"
|
||||||
|
} else {
|
||||||
|
s" $opcode ${AddrMode.addrModeToString(addrMode, parameter.toString)}"
|
||||||
|
}
|
||||||
|
}
|
33
src/main/scala/millfork/assembly/Chunk.scala
Normal file
33
src/main/scala/millfork/assembly/Chunk.scala
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package millfork.assembly
|
||||||
|
|
||||||
|
import millfork.env.Label
|
||||||
|
|
||||||
|
sealed trait Chunk {
|
||||||
|
def linearize: List[AssemblyLine]
|
||||||
|
def sizeInBytes: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
case object EmptyChunk extends Chunk {
|
||||||
|
override def linearize: Nil.type = Nil
|
||||||
|
|
||||||
|
override def sizeInBytes = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
case class LabelledChunk(label: String, chunk: Chunk) extends Chunk {
|
||||||
|
override def linearize: List[AssemblyLine] = AssemblyLine.label(Label(label)) :: chunk.linearize
|
||||||
|
|
||||||
|
override def sizeInBytes: Int = chunk.sizeInBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SequenceChunk(chunks: List[Chunk]) extends Chunk {
|
||||||
|
override def linearize: List[AssemblyLine] = chunks.flatMap(_.linearize)
|
||||||
|
|
||||||
|
override def sizeInBytes: Int = chunks.map(_.sizeInBytes).sum
|
||||||
|
}
|
||||||
|
|
||||||
|
case class LinearChunk(lines: List[AssemblyLine]) extends Chunk {
|
||||||
|
def linearize: List[AssemblyLine] = lines
|
||||||
|
|
||||||
|
override def sizeInBytes: Int = lines.map(_.sizeInBytes).sum
|
||||||
|
|
||||||
|
}
|
190
src/main/scala/millfork/assembly/Opcode.scala
Normal file
190
src/main/scala/millfork/assembly/Opcode.scala
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
package millfork.assembly
|
||||||
|
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node.Position
|
||||||
|
|
||||||
|
object State extends Enumeration {
|
||||||
|
val A, X, Y, Z, D, C, N, V = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
object Treatment extends Enumeration {
|
||||||
|
val Unchanged, Unsure, Changed, Cleared, Set = Value
|
||||||
|
|
||||||
|
implicit class OverriddenValue(val left: Value) extends AnyVal {
|
||||||
|
def ~(right: Treatment.Value): Treatment.Value = right match {
|
||||||
|
case Unchanged => left
|
||||||
|
case Cleared | Set => if (left == Unsure) Changed else right
|
||||||
|
case _ => right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object Opcode extends Enumeration {
|
||||||
|
val ADC, AND, ASL,
|
||||||
|
BIT, BNE, BEQ, BPL, BMI, BVS, BVC, BCC, BCS, BRK,
|
||||||
|
CMP, CPX, CPY, CLV, CLC, CLI, CLD,
|
||||||
|
DEC, DEX, DEY,
|
||||||
|
EOR,
|
||||||
|
INC, INX, INY,
|
||||||
|
JMP, JSR,
|
||||||
|
LDA, LDX, LDY, LSR,
|
||||||
|
NOP,
|
||||||
|
ORA,
|
||||||
|
PHA, PHP, PLA, PLP,
|
||||||
|
ROL, ROR, RTS, RTI,
|
||||||
|
SBC, SEC, SED, SEI, STA, STX, STY,
|
||||||
|
TAX, TAY, TXA, TXS, TSX, TYA,
|
||||||
|
|
||||||
|
LXA, XAA, ANC, ARR, ALR, SBX,
|
||||||
|
LAX, SAX, RLA, RRA, SLO, SRE, DCP, ISC,
|
||||||
|
TAS, LAS, SHX, SHY, AHX,
|
||||||
|
STZ, PHX, PHY, PLX, PLY,
|
||||||
|
BRA, TRB, TSB, STP, WAI,
|
||||||
|
DISCARD_AF, DISCARD_XF, DISCARD_YF,
|
||||||
|
LABEL = Value
|
||||||
|
|
||||||
|
def lookup(opcode: String, position: Option[Position]): Opcode.Value = opcode.toUpperCase(Locale.ROOT) match {
|
||||||
|
case "ADC" => ADC
|
||||||
|
case "AHX" => AHX
|
||||||
|
case "ALR" => ALR
|
||||||
|
case "ANC" => ANC
|
||||||
|
case "AND" => AND
|
||||||
|
case "ANE" => XAA
|
||||||
|
case "ARR" => ARR
|
||||||
|
case "ASL" => ASL
|
||||||
|
case "ASO" => SLO
|
||||||
|
case "AXA" => AHX
|
||||||
|
case "AXS" => SBX // TODO: could mean SAX
|
||||||
|
case "BCC" => BCC
|
||||||
|
case "BCS" => BCS
|
||||||
|
case "BEQ" => BEQ
|
||||||
|
case "BIT" => BIT
|
||||||
|
case "BMI" => BMI
|
||||||
|
case "BNE" => BNE
|
||||||
|
case "BPL" => BPL
|
||||||
|
case "BRA" => BRA
|
||||||
|
case "BRK" => BRK
|
||||||
|
case "BVC" => BVC
|
||||||
|
case "BVS" => BVS
|
||||||
|
case "CLC" => CLC
|
||||||
|
case "CLD" => CLD
|
||||||
|
case "CLI" => CLI
|
||||||
|
case "CLV" => CLV
|
||||||
|
case "CMP" => CMP
|
||||||
|
case "CPX" => CPX
|
||||||
|
case "CPY" => CPY
|
||||||
|
case "DCM" => DCP
|
||||||
|
case "DCP" => DCP
|
||||||
|
case "DEC" => DEC
|
||||||
|
case "DEX" => DEX
|
||||||
|
case "DEY" => DEY
|
||||||
|
case "EOR" => EOR
|
||||||
|
case "INC" => INC
|
||||||
|
case "INS" => ISC
|
||||||
|
case "INX" => INX
|
||||||
|
case "INY" => INY
|
||||||
|
case "ISC" => ISC
|
||||||
|
case "JMP" => JMP
|
||||||
|
case "JSR" => JSR
|
||||||
|
case "LAS" => LAS
|
||||||
|
case "LAX" => LAX
|
||||||
|
case "LDA" => LDA
|
||||||
|
case "LDX" => LDX
|
||||||
|
case "LDY" => LDY
|
||||||
|
case "LSE" => SRE
|
||||||
|
case "LSR" => LSR
|
||||||
|
case "LXA" => LXA
|
||||||
|
case "NOP" => NOP
|
||||||
|
case "OAL" => LXA
|
||||||
|
case "ORA" => ORA
|
||||||
|
case "PHA" => PHA
|
||||||
|
case "PHP" => PHP
|
||||||
|
case "PHX" => PHX
|
||||||
|
case "PHY" => PHY
|
||||||
|
case "PLA" => PLA
|
||||||
|
case "PLP" => PLP
|
||||||
|
case "PLX" => PLX
|
||||||
|
case "PLY" => PLY
|
||||||
|
case "RLA" => RLA
|
||||||
|
case "ROL" => ROL
|
||||||
|
case "ROR" => ROR
|
||||||
|
case "RRA" => RRA
|
||||||
|
case "RTI" => RTI
|
||||||
|
case "RTS" => RTS
|
||||||
|
case "SAX" => SAX // TODO: could mean SBX
|
||||||
|
case "SAY" => SHY
|
||||||
|
case "SBC" => SBC
|
||||||
|
case "SBX" => SBX
|
||||||
|
case "SEC" => SEC
|
||||||
|
case "SED" => SED
|
||||||
|
case "SEI" => SEI
|
||||||
|
case "SHX" => SHX
|
||||||
|
case "SHY" => SHY
|
||||||
|
case "SLO" => SLO
|
||||||
|
case "SRE" => SRE
|
||||||
|
case "STA" => STA
|
||||||
|
case "STP" => STP
|
||||||
|
case "STX" => STX
|
||||||
|
case "STY" => STY
|
||||||
|
case "STZ" => STZ
|
||||||
|
case "TAS" => TAS
|
||||||
|
case "TAX" => TAX
|
||||||
|
case "TAY" => TAY
|
||||||
|
case "TRB" => TRB
|
||||||
|
case "TSB" => TSB
|
||||||
|
case "TSX" => TSX
|
||||||
|
case "TXA" => TXA
|
||||||
|
case "TXS" => TXS
|
||||||
|
case "TYA" => TYA
|
||||||
|
case "WAI" => WAI
|
||||||
|
case "XAA" => XAA
|
||||||
|
case "XAS" => SHX
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error(s"Invalid opcode `$opcode`", position)
|
||||||
|
LABEL
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object AddrMode extends Enumeration {
|
||||||
|
val Implied,
|
||||||
|
Immediate,
|
||||||
|
Relative,
|
||||||
|
ZeroPage,
|
||||||
|
ZeroPageX,
|
||||||
|
ZeroPageY,
|
||||||
|
Absolute,
|
||||||
|
AbsoluteX,
|
||||||
|
AbsoluteY,
|
||||||
|
Indirect,
|
||||||
|
IndexedX,
|
||||||
|
IndexedY,
|
||||||
|
AbsoluteIndexedX,
|
||||||
|
ZeroPageIndirect,
|
||||||
|
Undecided,
|
||||||
|
DoesNotExist = Value
|
||||||
|
|
||||||
|
|
||||||
|
def argumentLength(a: AddrMode.Value): Int = a match {
|
||||||
|
case Absolute | AbsoluteX | AbsoluteY | Indirect =>
|
||||||
|
2
|
||||||
|
case _ =>
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
def addrModeToString(am: AddrMode.Value, argument: String): String = {
|
||||||
|
am match {
|
||||||
|
case Implied => ""
|
||||||
|
case Immediate => "#" + argument
|
||||||
|
case AbsoluteX | ZeroPageX => argument + ", X"
|
||||||
|
case AbsoluteY | ZeroPageY => argument + ", Y"
|
||||||
|
case IndexedX | AbsoluteIndexedX => "(" + argument + ", X)"
|
||||||
|
case IndexedY => "(" + argument + "), Y"
|
||||||
|
case Indirect | ZeroPageIndirect => "(" + argument + ")"
|
||||||
|
case _ => argument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,848 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
import millfork.assembly.{opt, _}
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.assembly.OpcodeClasses._
|
||||||
|
import millfork.env._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These optimizations should not remove opportunities for more complex optimizations to trigger.
|
||||||
|
*
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object AlwaysGoodOptimizations {
|
||||||
|
|
||||||
|
val counter = new AtomicInteger(30000)
|
||||||
|
|
||||||
|
def getNextLabel(prefix: String) = f".${prefix}%s__${counter.getAndIncrement()}%05d"
|
||||||
|
|
||||||
|
val PointlessMath = new RuleBasedAssemblyOptimization("Pointless math",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(HasOpcode(CLC) & Elidable) ~
|
||||||
|
(HasOpcode(ADC) & Elidable & MatchParameter(0)) ~
|
||||||
|
(HasOpcode(SEC) & Elidable) ~
|
||||||
|
(HasOpcode(SBC) & Elidable & MatchParameter(0)) ~
|
||||||
|
(LinearOrLabel & Not(ReadsNOrZ) & Not(ReadsV) & Not(ReadsC) & Not(NoopDiscardsFlags) & Not(Set(ADC, SBC))).* ~
|
||||||
|
(NoopDiscardsFlags | Set(ADC, SBC)) ~~> (_.drop(4)),
|
||||||
|
(HasOpcode(LDA) & HasImmediate(0) & Elidable) ~
|
||||||
|
(HasOpcode(CLC) & Elidable) ~
|
||||||
|
(HasOpcode(ADC) & Elidable) ~
|
||||||
|
(LinearOrLabel & Not(ReadsV) & Not(NoopDiscardsFlags) & Not(ChangesNAndZ)).* ~
|
||||||
|
(NoopDiscardsFlags | ChangesNAndZ) ~~> (code => code(2).copy(opcode = LDA) :: code.drop(3))
|
||||||
|
)
|
||||||
|
|
||||||
|
val PointlessMathFromFlow = new RuleBasedAssemblyOptimization("Pointless math from flow analysis",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BothFlows,
|
||||||
|
(Elidable & MatchA(0) &
|
||||||
|
HasOpcode(ASL) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, ctx.get[Int](0) << 1) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable & MatchA(0) &
|
||||||
|
HasOpcode(LSR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, (ctx.get[Int](0) & 0xff) >> 1) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable & MatchA(0) &
|
||||||
|
HasClear(State.C) & HasOpcode(ROL) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, ctx.get[Int](0) << 1) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable & MatchA(0) &
|
||||||
|
HasClear(State.C) & HasOpcode(ROR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, (ctx.get[Int](0) & 0xff) >> 1) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable & MatchA(0) &
|
||||||
|
HasSet(State.C) & HasOpcode(ROL) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, ctx.get[Int](0) * 2 + 1) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable & MatchA(0) &
|
||||||
|
HasSet(State.C) & HasOpcode(ROR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, 0x80 + (ctx.get[Int](0) & 0xff) / 2) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable &
|
||||||
|
MatchA(0) & MatchParameter(1) &
|
||||||
|
HasOpcode(ADC) & HasAddrMode(Immediate) &
|
||||||
|
HasClear(State.D) & HasClear(State.C) & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, ctx.get[Constant](1) + ctx.get[Int](0)) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable &
|
||||||
|
MatchA(0) & MatchParameter(1) &
|
||||||
|
HasOpcode(ADC) & HasAddrMode(Immediate) &
|
||||||
|
HasClear(State.D) & HasClear(State.C) & DoesntMatterWhatItDoesWith(State.V)) ~
|
||||||
|
Where(ctx => (ctx.get[Constant](1) + ctx.get[Int](0)).quickSimplify match {
|
||||||
|
case NumericConstant(x, _) => x == (x & 0xff)
|
||||||
|
case _ => false
|
||||||
|
}) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, ctx.get[Constant](1) + ctx.get[Int](0)) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable &
|
||||||
|
MatchA(0) & MatchParameter(1) &
|
||||||
|
HasOpcode(ADC) & HasAddrMode(Immediate) &
|
||||||
|
HasClear(State.D) & HasSet(State.C) & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, ctx.get[Constant](1) + ((ctx.get[Int](0) + 1) & 0xff)) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable &
|
||||||
|
MatchA(0) & MatchParameter(1) &
|
||||||
|
HasOpcode(SBC) & HasAddrMode(Immediate) &
|
||||||
|
HasClear(State.D) & HasSet(State.C) & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.Minus, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable &
|
||||||
|
MatchA(0) & MatchParameter(1) &
|
||||||
|
HasOpcode(EOR) & HasAddrMode(Immediate)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.Exor, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable &
|
||||||
|
MatchA(0) & MatchParameter(1) &
|
||||||
|
HasOpcode(ORA) & HasAddrMode(Immediate)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.Or, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable &
|
||||||
|
MatchA(0) & MatchParameter(1) &
|
||||||
|
HasOpcode(AND) & HasAddrMode(Immediate)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.And, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable &
|
||||||
|
MatchA(0) & MatchParameter(1) &
|
||||||
|
HasOpcode(ANC) & HasAddrMode(Immediate) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.And, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val MathOperationOnTwoIdenticalMemoryOperands = new RuleBasedAssemblyOptimization("Math operation on two identical memory operands",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BothFlows,
|
||||||
|
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~
|
||||||
|
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA)).* ~
|
||||||
|
(HasClear(State.D) & HasClear(State.C) & HasOpcode(ADC) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.implied(ASL)),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(STA, LDA)) & HasAddrMode(AbsoluteX) & MatchAddrMode(9) & MatchParameter(0)) ~
|
||||||
|
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA) & Not(ChangesX)).* ~
|
||||||
|
(HasClear(State.D) & HasClear(State.C) & HasOpcode(ADC) & HasAddrMode(AbsoluteX) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.implied(ASL)),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrMode(AbsoluteY) & MatchAddrMode(9) & MatchParameter(0)) ~
|
||||||
|
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA) & Not(ChangesY)).* ~
|
||||||
|
(HasClear(State.D) & HasClear(State.C) & HasOpcode(ADC) & HasAddrMode(AbsoluteY) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.implied(ASL)),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~
|
||||||
|
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA)).* ~
|
||||||
|
(DoesntMatterWhatItDoesWith(State.N, State.Z) & HasOpcodeIn(Set(ORA, AND)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(0) & Elidable) ~~> (code => code.init),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~
|
||||||
|
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA)).* ~
|
||||||
|
(DoesntMatterWhatItDoesWith(State.N, State.Z, State.C) & HasOpcode(ANC) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(0) & Elidable) ~~> (code => code.init),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~
|
||||||
|
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA)).* ~
|
||||||
|
(HasOpcode(EOR) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.immediate(LDA, 0)),
|
||||||
|
)
|
||||||
|
|
||||||
|
val PointlessStoreAfterLoad = new RuleBasedAssemblyOptimization("Pointless store after load",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(LinearOrLabel & DoesntChangeMemoryAt(0,1) & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0)).* ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
(HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(LinearOrLabel & DoesntChangeMemoryAt(0,1) & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0)).* ~
|
||||||
|
(Elidable & HasOpcode(STX) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
(HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(LinearOrLabel & DoesntChangeMemoryAt(0,1) & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0)).* ~
|
||||||
|
(Elidable & HasOpcode(STY) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
)
|
||||||
|
|
||||||
|
val PoinlessStoreBeforeStore = new RuleBasedAssemblyOptimization("Pointless store before store",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STY, STZ)) ~
|
||||||
|
(LinearOrLabel & DoesNotConcernMemoryAt(2, 1)).* ~
|
||||||
|
(MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STY, STZ)) ~~> (_.tail),
|
||||||
|
(Elidable & HasAddrModeIn(Set(AbsoluteX, ZeroPageX)) & MatchParameter(1) & MatchAddrMode(2) & Set(STA, STY, STZ)) ~
|
||||||
|
(LinearOrLabel & DoesntChangeMemoryAt(2, 1) & Not(ReadsMemory) & Not(ChangesX)).* ~
|
||||||
|
(MatchParameter(1) & MatchAddrMode(2) & Set(STA, STY, STZ)) ~~> (_.tail),
|
||||||
|
(Elidable & HasAddrModeIn(Set(AbsoluteY, ZeroPageY)) & MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STZ)) ~
|
||||||
|
(LinearOrLabel & DoesntChangeMemoryAt(2, 1) & Not(ReadsMemory) & Not(ChangesY)).* ~
|
||||||
|
(MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STZ)) ~~> (_.tail),
|
||||||
|
)
|
||||||
|
|
||||||
|
val PointlessLoadBeforeReturn = new RuleBasedAssemblyOptimization("Pointless load before return",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Set(LDA, TXA, TYA, EOR, AND, ORA, ANC) & Elidable) ~ (LinearOrLabel & Not(ConcernsA) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_AF))).* ~ HasOpcode(DISCARD_AF) ~~> (_.tail),
|
||||||
|
(Set(LDX, TAX, TSX, INX, DEX) & Elidable) ~ (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_XF))).* ~ HasOpcode(DISCARD_XF) ~~> (_.tail),
|
||||||
|
(Set(LDY, TAY, INY, DEY) & Elidable) ~ (LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_YF))).* ~ HasOpcode(DISCARD_YF) ~~> (_.tail),
|
||||||
|
(HasOpcode(LDX) & Elidable & MatchAddrMode(3)) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ) & DoesntChangeIndexingInAddrMode(3)).*.capture(1) ~
|
||||||
|
(HasOpcode(TXA) & Elidable) ~
|
||||||
|
((LinearOrLabel & Not(ConcernsX) & Not(HasOpcode(DISCARD_XF))).* ~
|
||||||
|
HasOpcode(DISCARD_XF)).capture(2) ~~> { (c, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](1) ++ (c.head.copy(opcode = LDA) :: ctx.get[List[AssemblyLine]](2))
|
||||||
|
},
|
||||||
|
(HasOpcode(LDY) & Elidable & MatchAddrMode(3)) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ) & DoesntChangeIndexingInAddrMode(3)).*.capture(1) ~
|
||||||
|
(HasOpcode(TYA) & Elidable) ~
|
||||||
|
((LinearOrLabel & Not(ConcernsY) & Not(HasOpcode(DISCARD_YF))).* ~
|
||||||
|
HasOpcode(DISCARD_YF)).capture(2) ~~> { (c, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](1) ++ (c.head.copy(opcode = LDA) :: ctx.get[List[AssemblyLine]](2))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
private def operationPairBuilder(op1: Opcode.Value, op2: Opcode.Value, middle: AssemblyLinePattern) = {
|
||||||
|
(HasOpcode(op1) & Elidable) ~
|
||||||
|
(Linear & middle).*.capture(1) ~
|
||||||
|
(HasOpcode(op2) & Elidable) ~
|
||||||
|
((LinearOrLabel & Not(ReadsNOrZ) & Not(ChangesNAndZ)).* ~ ChangesNAndZ).capture(2) ~~> { (_, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](1) ++ ctx.get[List[AssemblyLine]](2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val PointlessOperationPairRemoval = new RuleBasedAssemblyOptimization("Pointless operation pair",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
operationPairBuilder(PHA, PLA, Not(ConcernsA) & Not(ConcernsStack)),
|
||||||
|
operationPairBuilder(PHX, PLX, Not(ConcernsX) & Not(ConcernsStack)),
|
||||||
|
operationPairBuilder(PHY, PLY, Not(ConcernsY) & Not(ConcernsStack)),
|
||||||
|
operationPairBuilder(INX, DEX, Not(ConcernsX) & Not(ReadsNOrZ)),
|
||||||
|
operationPairBuilder(DEX, INX, Not(ConcernsX) & Not(ReadsNOrZ)),
|
||||||
|
operationPairBuilder(INY, DEY, Not(ConcernsX) & Not(ReadsNOrZ)),
|
||||||
|
operationPairBuilder(DEY, INY, Not(ConcernsX) & Not(ReadsNOrZ)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val BranchInPlaceRemoval = new RuleBasedAssemblyOptimization("Branch in place",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(AllDirectJumps & MatchParameter(0) & Elidable) ~
|
||||||
|
HasOpcodeIn(NoopDiscardsFlags).* ~
|
||||||
|
(HasOpcode(LABEL) & MatchParameter(0)) ~~> (c => c.last :: Nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
val ImpossibleBranchRemoval = new RuleBasedAssemblyOptimization("Impossible branch",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
|
||||||
|
(HasOpcode(BCC) & HasSet(State.C) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasOpcode(BCS) & HasClear(State.C) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasOpcode(BVC) & HasSet(State.V) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasOpcode(BVS) & HasClear(State.V) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasOpcode(BNE) & HasSet(State.Z) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasOpcode(BEQ) & HasClear(State.Z) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasOpcode(BPL) & HasSet(State.N) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasOpcode(BMI) & HasClear(State.N) & Elidable) ~~> (_ => Nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
val UnconditionalJumpRemoval = new RuleBasedAssemblyOptimization("Unconditional jump removal",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasOpcode(JMP) & HasAddrMode(Absolute) & MatchParameter(0)) ~
|
||||||
|
(Elidable & LinearOrBranch).* ~
|
||||||
|
(HasOpcode(LABEL) & MatchParameter(0)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcode(JMP) & HasAddrMode(Absolute) & MatchParameter(0)) ~
|
||||||
|
(Not(HasOpcode(LABEL)) & Not(MatchParameter(0))).* ~
|
||||||
|
(HasOpcode(LABEL) & MatchParameter(0)) ~
|
||||||
|
(HasOpcode(LABEL) | HasOpcodeIn(NoopDiscardsFlags)).* ~
|
||||||
|
HasOpcode(RTS) ~~> (code => AssemblyLine.implied(RTS) :: code.tail),
|
||||||
|
(Elidable & HasOpcodeIn(ShortBranching) & MatchParameter(0)) ~
|
||||||
|
(HasOpcodeIn(NoopDiscardsFlags).* ~
|
||||||
|
(Elidable & HasOpcode(RTS))).capture(1) ~
|
||||||
|
(HasOpcode(LABEL) & MatchParameter(0)) ~
|
||||||
|
HasOpcodeIn(NoopDiscardsFlags).* ~
|
||||||
|
(Elidable & HasOpcode(RTS)) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1)),
|
||||||
|
)
|
||||||
|
|
||||||
|
val TailCallOptimization = new RuleBasedAssemblyOptimization("Tail call optimization",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasOpcode(JSR)) ~ HasOpcodeIn(NoopDiscardsFlags).* ~ (Elidable & HasOpcode(RTS)) ~~> (c => c.tail.init :+ c.head.copy(opcode = JMP)),
|
||||||
|
(Elidable & HasOpcode(JSR)) ~
|
||||||
|
HasOpcode(LABEL).* ~
|
||||||
|
HasOpcodeIn(NoopDiscardsFlags).*.capture(0) ~
|
||||||
|
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](0) ++ (code.head.copy(opcode = JMP) :: code.tail)),
|
||||||
|
)
|
||||||
|
|
||||||
|
val UnusedCodeRemoval = new RuleBasedAssemblyOptimization("Unreachable code removal",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
HasOpcode(JMP) ~ (Not(HasOpcode(LABEL)) & Elidable).+ ~~> (c => c.head :: Nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
val PoinlessFlagChange = new RuleBasedAssemblyOptimization("Pointless flag change",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(HasOpcodeIn(Set(CMP, CPX, CPY)) & Elidable) ~ NoopDiscardsFlags ~~> (_.tail),
|
||||||
|
(OverwritesC & Elidable) ~ (LinearOrLabel & Not(ReadsC) & Not(DiscardsC)).* ~ DiscardsC ~~> (_.tail),
|
||||||
|
(OverwritesD & Elidable) ~ (LinearOrLabel & Not(ReadsD) & Not(DiscardsD)).* ~ DiscardsD ~~> (_.tail),
|
||||||
|
(OverwritesV & Elidable) ~ (LinearOrLabel & Not(ReadsV) & Not(DiscardsV)).* ~ DiscardsV ~~> (_.tail)
|
||||||
|
)
|
||||||
|
|
||||||
|
val FlagFlowAnalysis = new RuleBasedAssemblyOptimization("Flag flow analysis",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
|
||||||
|
(HasSet(State.C) & HasOpcode(SEC) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasSet(State.D) & HasOpcode(SED) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasClear(State.C) & HasOpcode(CLC) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasClear(State.D) & HasOpcode(CLD) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasClear(State.V) & HasOpcode(CLV) & Elidable) ~~> (_ => Nil),
|
||||||
|
(HasSet(State.C) & HasOpcode(BCS) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
|
||||||
|
(HasClear(State.C) & HasOpcode(BCC) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
|
||||||
|
(HasSet(State.N) & HasOpcode(BMI) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
|
||||||
|
(HasClear(State.N) & HasOpcode(BPL) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
|
||||||
|
(HasClear(State.V) & HasOpcode(BVC) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
|
||||||
|
(HasSet(State.V) & HasOpcode(BVS) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
|
||||||
|
(HasSet(State.Z) & HasOpcode(BEQ) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
|
||||||
|
(HasClear(State.Z) & HasOpcode(BNE) & Elidable) ~~> (_ => Nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
val ReverseFlowAnalysis = new RuleBasedAssemblyOptimization("Reverse flow analysis",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
(Elidable & HasOpcodeIn(Set(TXA, TYA, LDA, EOR, ORA, AND)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcode(ANC) & DoesntMatterWhatItDoesWith(State.A, State.C, State.N, State.Z)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcodeIn(Set(TAX, TSX, LDX, INX, DEX)) & DoesntMatterWhatItDoesWith(State.X, State.N, State.Z)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcodeIn(Set(TAY, LDY, DEY, INY)) & DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcodeIn(Set(LAX)) & DoesntMatterWhatItDoesWith(State.A, State.X, State.N, State.Z)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcodeIn(Set(SEC, CLC)) & DoesntMatterWhatItDoesWith(State.C)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcodeIn(Set(CLD, SED)) & DoesntMatterWhatItDoesWith(State.D)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcode(CLV) & DoesntMatterWhatItDoesWith(State.V)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcodeIn(Set(CMP, CPX, CPY)) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcodeIn(Set(BIT)) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z, State.V)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcodeIn(Set(ASL, LSR, ROL, ROR)) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.A, State.C, State.N, State.Z)) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcodeIn(Set(ADC, SBC)) & DoesntMatterWhatItDoesWith(State.A, State.C, State.V, State.N, State.Z)) ~~> (_ => Nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
private def modificationOfJustWrittenValue(store: Opcode.Value,
|
||||||
|
addrMode: AddrMode.Value,
|
||||||
|
initExtra: AssemblyLinePattern,
|
||||||
|
modify: Opcode.Value,
|
||||||
|
meantimeExtra: AssemblyLinePattern,
|
||||||
|
atLeastTwo: Boolean,
|
||||||
|
flagsToTrash: Seq[State.Value],
|
||||||
|
fix: ((AssemblyMatchingContext, Int) => List[AssemblyLine]),
|
||||||
|
alternateStore: Opcode.Value = LABEL) = {
|
||||||
|
val actualFlagsToTrash = List(State.N, State.Z) ++ flagsToTrash
|
||||||
|
val init = Elidable & HasOpcode(store) & HasAddrMode(addrMode) & MatchAddrMode(3) & MatchParameter(0) & DoesntMatterWhatItDoesWith(actualFlagsToTrash: _*) & initExtra
|
||||||
|
val meantime = (Linear & Not(ConcernsMemory) & meantimeExtra).*
|
||||||
|
val oneModification = Elidable & HasOpcode(modify) & HasAddrMode(addrMode) & MatchParameter(0) & DoesntMatterWhatItDoesWith(actualFlagsToTrash: _*)
|
||||||
|
val modifications = (if (atLeastTwo) oneModification ~ oneModification.+ else oneModification.+).captureLength(1)
|
||||||
|
if (alternateStore == LABEL) {
|
||||||
|
((init ~ meantime).capture(2) ~ modifications) ~~> ((code, ctx) => fix(ctx, ctx.get[Int](1)) ++ ctx.get[List[AssemblyLine]](2))
|
||||||
|
} else {
|
||||||
|
(init.capture(3) ~ meantime.capture(2) ~ modifications) ~~> { (code, ctx) =>
|
||||||
|
fix(ctx, ctx.get[Int](1)) ++
|
||||||
|
List(AssemblyLine(alternateStore, ctx.get[AddrMode.Value](3), ctx.get[Constant](0))) ++
|
||||||
|
ctx.get[List[AssemblyLine]](2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val ModificationOfJustWrittenValue = new RuleBasedAssemblyOptimization("Modification of Just written value",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
|
||||||
|
modificationOfJustWrittenValue(STA, Absolute, MatchA(5), INC, Anything, atLeastTwo = false, Seq(), (c, i) => List(
|
||||||
|
AssemblyLine.immediate(LDA, (c.get[Int](5) + i) & 0xff)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, Absolute, MatchA(5), DEC, Anything, atLeastTwo = false, Seq(), (c, i) => List(
|
||||||
|
AssemblyLine.immediate(LDA, (c.get[Int](5) - i) & 0xff)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, ZeroPage, MatchA(5), INC, Anything, atLeastTwo = false, Seq(), (c, i) => List(
|
||||||
|
AssemblyLine.immediate(LDA, (c.get[Int](5) + i) & 0xff)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, ZeroPage, MatchA(5), DEC, Anything, atLeastTwo = false, Seq(), (c, i) => List(
|
||||||
|
AssemblyLine.immediate(LDA, (c.get[Int](5) - i) & 0xff)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, AbsoluteX, MatchA(5), INC, Not(ChangesX), atLeastTwo = false, Seq(), (c, i) => List(
|
||||||
|
AssemblyLine.immediate(LDA, (c.get[Int](5) + i) & 0xff)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, AbsoluteX, MatchA(5), DEC, Not(ChangesX), atLeastTwo = false, Seq(), (c, i) => List(
|
||||||
|
AssemblyLine.immediate(LDA, (c.get[Int](5) - i) & 0xff)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, Absolute, Anything, INC, Anything, atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
|
||||||
|
AssemblyLine.implied(CLC),
|
||||||
|
AssemblyLine.immediate(ADC, i)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, Absolute, Anything, DEC, Anything, atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
|
||||||
|
AssemblyLine.implied(SEC),
|
||||||
|
AssemblyLine.immediate(SBC, i)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, ZeroPage, Anything, INC, Anything, atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
|
||||||
|
AssemblyLine.implied(CLC),
|
||||||
|
AssemblyLine.immediate(ADC, i)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, ZeroPage, Anything, DEC, Anything, atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
|
||||||
|
AssemblyLine.implied(SEC),
|
||||||
|
AssemblyLine.immediate(SBC, i)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, AbsoluteX, Anything, INC, Not(ChangesX), atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
|
||||||
|
AssemblyLine.implied(CLC),
|
||||||
|
AssemblyLine.immediate(ADC, i)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, AbsoluteX, Anything, DEC, Not(ChangesX), atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
|
||||||
|
AssemblyLine.implied(SEC),
|
||||||
|
AssemblyLine.immediate(SBC, i)
|
||||||
|
)),
|
||||||
|
modificationOfJustWrittenValue(STA, Absolute, Anything, ASL, Anything, atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(ASL))),
|
||||||
|
modificationOfJustWrittenValue(STA, Absolute, Anything, LSR, Anything, atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(LSR))),
|
||||||
|
modificationOfJustWrittenValue(STA, ZeroPage, Anything, ASL, Anything, atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(ASL))),
|
||||||
|
modificationOfJustWrittenValue(STA, ZeroPage, Anything, LSR, Anything, atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(LSR))),
|
||||||
|
modificationOfJustWrittenValue(STA, AbsoluteX, Anything, ASL, Not(ChangesX), atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(ASL))),
|
||||||
|
modificationOfJustWrittenValue(STA, AbsoluteX, Anything, LSR, Not(ChangesX), atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(LSR))),
|
||||||
|
modificationOfJustWrittenValue(STX, Absolute, Anything, INC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(INX))),
|
||||||
|
modificationOfJustWrittenValue(STX, Absolute, Anything, DEC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(DEX))),
|
||||||
|
modificationOfJustWrittenValue(STY, Absolute, Anything, INC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(INY))),
|
||||||
|
modificationOfJustWrittenValue(STY, Absolute, Anything, DEC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(DEY))),
|
||||||
|
modificationOfJustWrittenValue(STZ, Absolute, Anything, ASL, Anything, atLeastTwo = false, Seq(), (_, i) => Nil),
|
||||||
|
modificationOfJustWrittenValue(STZ, Absolute, Anything, LSR, Anything, atLeastTwo = false, Seq(), (_, i) => Nil),
|
||||||
|
modificationOfJustWrittenValue(STZ, Absolute, Anything, INC, Anything, atLeastTwo = false, Seq(State.A), (_, i) => List(AssemblyLine.immediate(LDA, i)), STA),
|
||||||
|
modificationOfJustWrittenValue(STZ, Absolute, Anything, DEC, Anything, atLeastTwo = false, Seq(State.A), (_, i) => List(AssemblyLine.immediate(LDA, 256 - i)), STA),
|
||||||
|
modificationOfJustWrittenValue(STX, ZeroPage, Anything, INC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(INX))),
|
||||||
|
modificationOfJustWrittenValue(STX, ZeroPage, Anything, DEC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(DEX))),
|
||||||
|
modificationOfJustWrittenValue(STY, ZeroPage, Anything, INC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(INY))),
|
||||||
|
modificationOfJustWrittenValue(STY, ZeroPage, Anything, DEC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(DEY))),
|
||||||
|
modificationOfJustWrittenValue(STZ, ZeroPage, Anything, ASL, Anything, atLeastTwo = false, Seq(), (_, i) => Nil),
|
||||||
|
modificationOfJustWrittenValue(STZ, ZeroPage, Anything, LSR, Anything, atLeastTwo = false, Seq(), (_, i) => Nil),
|
||||||
|
modificationOfJustWrittenValue(STZ, ZeroPage, Anything, INC, Anything, atLeastTwo = false, Seq(State.A), (_, i) => List(AssemblyLine.immediate(LDA, i)), STA),
|
||||||
|
modificationOfJustWrittenValue(STZ, ZeroPage, Anything, DEC, Anything, atLeastTwo = false, Seq(State.A), (_, i) => List(AssemblyLine.immediate(LDA, 256 - i)), STA),
|
||||||
|
)
|
||||||
|
|
||||||
|
val ConstantFlowAnalysis = new RuleBasedAssemblyOptimization("Constant flow analysis",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
|
||||||
|
(MatchX(0) & HasAddrMode(AbsoluteX) & SupportsAbsolute & Elidable) ~~> { (code, ctx) =>
|
||||||
|
code.map(l => l.copy(addrMode = Absolute, parameter = l.parameter + ctx.get[Int](0)))
|
||||||
|
},
|
||||||
|
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable) ~~> { (code, ctx) =>
|
||||||
|
code.map(l => l.copy(addrMode = Absolute, parameter = l.parameter + ctx.get[Int](0)))
|
||||||
|
},
|
||||||
|
(MatchX(0) & HasAddrMode(ZeroPageX) & Elidable) ~~> { (code, ctx) =>
|
||||||
|
code.map(l => l.copy(addrMode = ZeroPage, parameter = l.parameter + ctx.get[Int](0)))
|
||||||
|
},
|
||||||
|
(MatchY(0) & HasAddrMode(ZeroPageY) & Elidable) ~~> { (code, ctx) =>
|
||||||
|
code.map(l => l.copy(addrMode = ZeroPage, parameter = l.parameter + ctx.get[Int](0)))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val IdempotentDuplicateRemoval = new RuleBasedAssemblyOptimization("Idempotent duplicate operation",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
HasOpcode(RTS) ~ HasOpcodeIn(NoopDiscardsFlags).* ~ (HasOpcode(RTS) ~ Elidable) ~~> (_.take(1)) ::
|
||||||
|
HasOpcode(RTI) ~ HasOpcodeIn(NoopDiscardsFlags).* ~ (HasOpcode(RTI) ~ Elidable) ~~> (_.take(1)) ::
|
||||||
|
HasOpcode(DISCARD_XF) ~ (Not(HasOpcode(DISCARD_XF)) & HasOpcodeIn(NoopDiscardsFlags + LABEL)).* ~ HasOpcode(DISCARD_XF) ~~> (_.tail) ::
|
||||||
|
HasOpcode(DISCARD_AF) ~ (Not(HasOpcode(DISCARD_AF)) & HasOpcodeIn(NoopDiscardsFlags + LABEL)).* ~ HasOpcode(DISCARD_AF) ~~> (_.tail) ::
|
||||||
|
HasOpcode(DISCARD_YF) ~ (Not(HasOpcode(DISCARD_YF)) & HasOpcodeIn(NoopDiscardsFlags + LABEL)).* ~ HasOpcode(DISCARD_YF) ~~> (_.tail) ::
|
||||||
|
List(RTS, RTI, SEC, CLC, CLV, CLD, SED, SEI, CLI, TAX, TXA, TYA, TAY, TXS, TSX).flatMap { opcode =>
|
||||||
|
Seq(
|
||||||
|
(HasOpcode(opcode) & Elidable) ~ (HasOpcodeIn(NoopDiscardsFlags) | HasOpcode(LABEL)).* ~ HasOpcode(opcode) ~~> (_.tail),
|
||||||
|
HasOpcode(opcode) ~ (HasOpcode(opcode) ~ Elidable) ~~> (_.init),
|
||||||
|
)
|
||||||
|
}: _*
|
||||||
|
)
|
||||||
|
|
||||||
|
val PointlessRegisterTransfers = new RuleBasedAssemblyOptimization("Pointless register transfers",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
HasOpcode(TYA) ~ (Elidable & Set(TYA, TAY)) ~~> (_.init),
|
||||||
|
HasOpcode(TXA) ~ (Elidable & Set(TXA, TAX)) ~~> (_.init),
|
||||||
|
HasOpcode(TAY) ~ (Elidable & Set(TYA, TAY)) ~~> (_.init),
|
||||||
|
HasOpcode(TAX) ~ (Elidable & Set(TXA, TAX)) ~~> (_.init),
|
||||||
|
HasOpcode(TSX) ~ (Elidable & Set(TXS, TSX)) ~~> (_.init),
|
||||||
|
HasOpcode(TXS) ~ (Elidable & Set(TXS, TSX)) ~~> (_.init),
|
||||||
|
HasOpcode(TSX) ~ (Not(ChangesX) & Not(ChangesS) & Linear).* ~ (Elidable & Set(TXS, TSX)) ~~> (_.init),
|
||||||
|
HasOpcode(TXS) ~ (Not(ChangesX) & Not(ChangesS) & Linear).* ~ (Elidable & Set(TXS, TSX)) ~~> (_.init),
|
||||||
|
)
|
||||||
|
|
||||||
|
val PointlessRegisterTransfersBeforeStore = new RuleBasedAssemblyOptimization("Pointless register transfers before store",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
(Elidable & HasOpcode(TXA)) ~
|
||||||
|
(Linear & Not(ConcernsA) & Not(ConcernsX)).* ~
|
||||||
|
(Elidable & HasOpcode(STA) & HasAddrModeIn(Set(ZeroPage, ZeroPageY, Absolute)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (code => code.tail.init :+ code.last.copy(opcode = STX)),
|
||||||
|
(Elidable & HasOpcode(TYA)) ~
|
||||||
|
(Linear & Not(ConcernsA) & Not(ConcernsY)).* ~
|
||||||
|
(Elidable & HasOpcode(STA) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (code => code.tail.init :+ code.last.copy(opcode = STY)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val PointlessRegisterTransfersBeforeReturn = new RuleBasedAssemblyOptimization("Pointless register transfers before return",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(HasOpcode(TAX) & Elidable) ~
|
||||||
|
HasOpcode(LABEL).* ~
|
||||||
|
HasOpcode(TXA).? ~
|
||||||
|
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_XF)).capture(1) ~
|
||||||
|
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
|
||||||
|
(HasOpcode(TSX) & Elidable) ~
|
||||||
|
HasOpcode(LABEL).* ~
|
||||||
|
HasOpcode(TSX).? ~
|
||||||
|
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_XF)).capture(1) ~
|
||||||
|
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
|
||||||
|
(HasOpcode(TXA) & Elidable) ~
|
||||||
|
HasOpcode(LABEL).* ~
|
||||||
|
HasOpcode(TAX).? ~
|
||||||
|
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_AF)).capture(1) ~
|
||||||
|
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
|
||||||
|
(HasOpcode(TAY) & Elidable) ~
|
||||||
|
HasOpcode(LABEL).* ~
|
||||||
|
HasOpcode(TYA).? ~
|
||||||
|
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_YF)).capture(1) ~
|
||||||
|
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
|
||||||
|
(HasOpcode(TYA) & Elidable) ~
|
||||||
|
HasOpcode(LABEL).* ~
|
||||||
|
HasOpcode(TAY).? ~
|
||||||
|
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_AF)).capture(1) ~
|
||||||
|
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
|
||||||
|
)
|
||||||
|
|
||||||
|
val PointlessRegisterTransfersBeforeCompare = new RuleBasedAssemblyOptimization("Pointless register transfers before compare",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
HasOpcodeIn(Set(DEX, INX, LDX, LAX)) ~
|
||||||
|
(HasOpcode(TXA) & Elidable & DoesntMatterWhatItDoesWith(State.A)) ~~> (code => code.init),
|
||||||
|
HasOpcodeIn(Set(DEY, INY, LDY)) ~
|
||||||
|
(HasOpcode(TYA) & Elidable & DoesntMatterWhatItDoesWith(State.A)) ~~> (code => code.init),
|
||||||
|
)
|
||||||
|
|
||||||
|
private def stashing(tai: Opcode.Value, tia: Opcode.Value, readsI: AssemblyLinePattern, concernsI: AssemblyLinePattern, discardIF: Opcode.Value, withRts: Boolean, withBeq: Boolean) = {
|
||||||
|
val init: AssemblyPattern = if (withBeq) {
|
||||||
|
(Linear & ChangesNAndZ & ChangesA) ~
|
||||||
|
(HasOpcode(tai) & Elidable) ~
|
||||||
|
(Linear & Not(concernsI) & Not(ChangesA) & Not(ReadsNOrZ)).* ~
|
||||||
|
(ShortBranching & ReadsNOrZ & MatchParameter(0))
|
||||||
|
} else {
|
||||||
|
(HasOpcode(tai) & Elidable) ~
|
||||||
|
(Linear & Not(concernsI) & Not(ChangesA) & Not(ReadsNOrZ)).* ~
|
||||||
|
((ShortBranching -- ReadsNOrZ) & MatchParameter(0))
|
||||||
|
}
|
||||||
|
val inner: AssemblyPattern = if (withRts) {
|
||||||
|
(Linear & Not(readsI) & Not(ReadsNOrZ ++ NoopDiscardsFlags)).* ~
|
||||||
|
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(discardIF)) ~
|
||||||
|
HasOpcodeIn(Set(RTS, RTI)) ~
|
||||||
|
Not(HasOpcode(LABEL)).*
|
||||||
|
} else {
|
||||||
|
(Linear & Not(concernsI) & Not(ChangesA) & Not(ReadsNOrZ)).*
|
||||||
|
}
|
||||||
|
val end: AssemblyPattern =
|
||||||
|
(HasOpcode(LABEL) & MatchParameter(0)) ~
|
||||||
|
(Linear & Not(concernsI) & Not(ChangesA) & Not(ReadsNOrZ)).* ~
|
||||||
|
(HasOpcode(tia) & Elidable)
|
||||||
|
val total = init ~ inner ~ end
|
||||||
|
if (withBeq) {
|
||||||
|
total ~~> (code => code.head :: (code.tail.tail.init :+ AssemblyLine.implied(tai)))
|
||||||
|
} else {
|
||||||
|
total ~~> (code => code.tail.init :+ AssemblyLine.implied(tai))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimize the following patterns:
|
||||||
|
// TAX - B__ .a - don't change A - .a - TXA
|
||||||
|
// TAX - B__ .a - change A – discard X – RTS - .a - TXA
|
||||||
|
// by removing the first transfer and flipping the second one
|
||||||
|
val PointlessStashingToIndexOverShortSafeBranch = new RuleBasedAssemblyOptimization("Pointless stashing into index over short safe branch",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
// stashing(TAX, TXA, ReadsX, ConcernsX, DISCARD_XF, withRts = false, withBeq = false),
|
||||||
|
stashing(TAX, TXA, ReadsX, ConcernsX, DISCARD_XF, withRts = true, withBeq = false),
|
||||||
|
// stashing(TAX, TXA, ReadsX, ConcernsX, DISCARD_XF, withRts = false, withBeq = true),
|
||||||
|
// stashing(TAX, TXA, ReadsX, ConcernsX, DISCARD_XF, withRts = true, withBeq = true),
|
||||||
|
//
|
||||||
|
// stashing(TAY, TYA, ReadsY, ConcernsY, DISCARD_YF, withRts = false, withBeq = false),
|
||||||
|
// stashing(TAY, TYA, ReadsY, ConcernsY, DISCARD_YF, withRts = true, withBeq = false),
|
||||||
|
// stashing(TAY, TYA, ReadsY, ConcernsY, DISCARD_YF, withRts = false, withBeq = true),
|
||||||
|
// stashing(TAY, TYA, ReadsY, ConcernsY, DISCARD_YF, withRts = true, withBeq = true),
|
||||||
|
)
|
||||||
|
|
||||||
|
private def loadBeforeTransfer(ld1: Opcode.Value, ld2: Opcode.Value, concerns1: AssemblyLinePattern, overwrites1: State.Value, t12: Opcode.Value, ams: Set[AddrMode.Value]) =
|
||||||
|
(Elidable & HasOpcode(ld1) & MatchAddrMode(0) & MatchParameter(1) & HasAddrModeIn(ams)) ~
|
||||||
|
(Linear & Not(ReadsNOrZ) & Not(concerns1) & DoesntChangeMemoryAt(0, 1) & DoesntChangeIndexingInAddrMode(0) & Not(HasOpcode(t12))).*.capture(2) ~
|
||||||
|
(HasOpcode(t12) & Elidable & DoesntMatterWhatItDoesWith(overwrites1, State.N, State.Z)) ~~> { (code, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](2) :+ code.head.copy(opcode = ld2)
|
||||||
|
}
|
||||||
|
|
||||||
|
val PointlessLoadBeforeTransfer = new RuleBasedAssemblyOptimization("Pointless load before transfer",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
loadBeforeTransfer(LDX, LDA, ConcernsX, State.X, TXA, Set(ZeroPage, Absolute, IndexedY, AbsoluteY)),
|
||||||
|
loadBeforeTransfer(LDA, LDX, ConcernsA, State.A, TAX, Set(ZeroPage, Absolute, IndexedY, AbsoluteY)),
|
||||||
|
loadBeforeTransfer(LDY, LDA, ConcernsY, State.Y, TYA, Set(ZeroPage, Absolute, ZeroPageX, IndexedX, AbsoluteX)),
|
||||||
|
loadBeforeTransfer(LDA, LDY, ConcernsA, State.A, TAY, Set(ZeroPage, Absolute, ZeroPageX, IndexedX, AbsoluteX)),
|
||||||
|
)
|
||||||
|
|
||||||
|
private def immediateLoadBeforeTwoTransfers(ld1: Opcode.Value, ld2: Opcode.Value, concerns1: AssemblyLinePattern, overwrites1: State.Value, t12: Opcode.Value, t21: Opcode.Value) =
|
||||||
|
(Elidable & HasOpcode(ld1) & HasAddrMode(Immediate)) ~
|
||||||
|
(Linear & Not(ReadsNOrZ) & Not(concerns1) & Not(HasOpcode(t12))).*.capture(2) ~
|
||||||
|
(HasOpcode(t12) & Elidable & DoesntMatterWhatItDoesWith(overwrites1, State.N, State.Z)) ~~> { (code, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](2) :+ code.head.copy(opcode = ld2)
|
||||||
|
}
|
||||||
|
|
||||||
|
val YYY = new RuleBasedAssemblyOptimization("Pointless load before transfer",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
immediateLoadBeforeTwoTransfers(LDA, LDY, ConcernsA, State.A, TAY, TYA),
|
||||||
|
)
|
||||||
|
|
||||||
|
val ConstantIndexPropagation = new RuleBasedAssemblyOptimization("Constant index propagation",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(HasOpcode(LDX) & HasAddrMode(Immediate) & MatchParameter(0)) ~
|
||||||
|
(Linear & Not(ChangesX) & Not(HasAddrMode(AbsoluteX))).* ~
|
||||||
|
(Elidable & SupportsAbsolute & HasAddrMode(AbsoluteX)) ~~> { (lines, ctx) =>
|
||||||
|
val last = lines.last
|
||||||
|
val offset = ctx.get[Constant](0)
|
||||||
|
lines.init :+ last.copy(addrMode = Absolute, parameter = last.parameter + offset)
|
||||||
|
},
|
||||||
|
(HasOpcode(LDY) & HasAddrMode(Immediate) & MatchParameter(0)) ~
|
||||||
|
(Linear & Not(ChangesY) & Not(HasAddrMode(AbsoluteY))).* ~
|
||||||
|
(Elidable & SupportsAbsolute & HasAddrMode(AbsoluteY)) ~~> { (lines, ctx) =>
|
||||||
|
val last = lines.last
|
||||||
|
val offset = ctx.get[Constant](0)
|
||||||
|
lines.init :+ last.copy(addrMode = Absolute, parameter = last.parameter + offset)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val PoinlessLoadBeforeAnotherLoad = new RuleBasedAssemblyOptimization("Pointless load before another load",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Set(LDA, TXA, TYA) & Elidable) ~ (LinearOrLabel & Not(ConcernsA) & Not(ReadsNOrZ)).* ~ OverwritesA ~~> (_.tail),
|
||||||
|
(Set(LDX, TAX, TSX) & Elidable) ~ (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ)).* ~ OverwritesX ~~> (_.tail),
|
||||||
|
(Set(LDY, TAY) & Elidable) ~ (LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ)).* ~ OverwritesY ~~> (_.tail),
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: better proofs that memory doesn't change
|
||||||
|
val PointlessLoadAfterLoadOrStore = new RuleBasedAssemblyOptimization("Pointless load after load or store",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(LDA, STA)) & HasAddrMode(Implied) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ChangesA)).* ~
|
||||||
|
(Elidable & HasOpcode(LDA) & HasAddrMode(Implied) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(LDX, STX)) & HasAddrMode(Implied) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ChangesX)).* ~
|
||||||
|
(Elidable & HasOpcode(LDX) & HasAddrMode(Implied) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(LDY, STY)) & HasAddrMode(Implied) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ChangesY)).* ~
|
||||||
|
(Elidable & HasOpcode(LDY) & HasAddrMode(Implied) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(LDA, STA)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(LDX, STX)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ChangesX) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
|
||||||
|
(Elidable & HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
|
(HasOpcodeIn(Set(LDY, STY)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ChangesY) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
|
||||||
|
(Elidable & HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
)
|
||||||
|
|
||||||
|
val PointlessOperationAfterLoad = new RuleBasedAssemblyOptimization("Pointless operation after load",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(ChangesA & ChangesNAndZ) ~ (Elidable & HasOpcode(EOR) & HasImmediate(0)) ~~> (_.init),
|
||||||
|
(ChangesA & ChangesNAndZ) ~ (Elidable & HasOpcode(ORA) & HasImmediate(0)) ~~> (_.init),
|
||||||
|
(ChangesA & ChangesNAndZ) ~ (Elidable & HasOpcode(AND) & HasImmediate(0xff)) ~~> (_.init)
|
||||||
|
)
|
||||||
|
|
||||||
|
val SimplifiableBitOpsSequence = new RuleBasedAssemblyOptimization("Simplifiable sequence of bit operations",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasOpcode(EOR) & MatchImmediate(0)) ~
|
||||||
|
(Linear & Not(ChangesA) & Not(ReadsNOrZ) & Not(ReadsA)).* ~
|
||||||
|
(Elidable & HasOpcode(EOR) & MatchImmediate(1)) ~~> { (lines, ctx) =>
|
||||||
|
lines.init.tail :+ AssemblyLine.immediate(EOR, CompoundConstant(MathOperator.Exor, ctx.get[Constant](0), ctx.get[Constant](1)))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(ORA) & MatchImmediate(0)) ~
|
||||||
|
(Linear & Not(ChangesA) & Not(ReadsNOrZ) & Not(ReadsA)).* ~
|
||||||
|
(Elidable & HasOpcode(ORA) & MatchImmediate(1)) ~~> { (lines, ctx) =>
|
||||||
|
lines.init.tail :+ AssemblyLine.immediate(ORA, CompoundConstant(MathOperator.Or, ctx.get[Constant](0), ctx.get[Constant](1)))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
|
||||||
|
(Linear & Not(ChangesA) & Not(ReadsNOrZ) & Not(ReadsA)).* ~
|
||||||
|
(Elidable & HasOpcode(AND) & MatchImmediate(1)) ~~> { (lines, ctx) =>
|
||||||
|
lines.init.tail :+ AssemblyLine.immediate(AND, CompoundConstant(MathOperator.And, ctx.get[Constant](0), ctx.get[Constant](1)))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(ANC) & MatchImmediate(0)) ~
|
||||||
|
(Linear & Not(ChangesA) & Not(ReadsNOrZ) & Not(ReadsC) & Not(ReadsA)).* ~
|
||||||
|
(Elidable & HasOpcode(ANC) & MatchImmediate(1)) ~~> { (lines, ctx) =>
|
||||||
|
lines.init.tail :+ AssemblyLine.immediate(ANC, CompoundConstant(MathOperator.And, ctx.get[Constant](0), ctx.get[Constant](1)))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val RemoveNops = new RuleBasedAssemblyOptimization("Removing NOP instructions",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasOpcode(NOP)) ~~> (_ => Nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
val RearrangeMath = new RuleBasedAssemblyOptimization("Rearranging math",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasOpcode(LDA) & HasAddrMode(Immediate)) ~
|
||||||
|
(Elidable & HasOpcodeIn(Set(CLC, SEC))) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & Not(HasAddrMode(Immediate))) ~~> { c =>
|
||||||
|
c.last.copy(opcode = LDA) :: c(1) :: c.head.copy(opcode = ADC) :: Nil
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDA) & HasAddrMode(Immediate)) ~
|
||||||
|
(Elidable & HasOpcodeIn(Set(ADC, EOR, ORA, AND)) & Not(HasAddrMode(Immediate))) ~~> { c =>
|
||||||
|
c.last.copy(opcode = LDA) :: c.head.copy(opcode = c.last.opcode) :: Nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
private def wordShifting(i: Int, hiFirst: Boolean, hiFromX: Boolean) = {
|
||||||
|
val ldax = if (hiFromX) LDX else LDA
|
||||||
|
val stax = if (hiFromX) STX else STA
|
||||||
|
val restriction = if (hiFromX) Not(ReadsX) else Anything
|
||||||
|
val originalStart = if (hiFirst) {
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchParameter(0) & MatchAddrMode(1)) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchParameter(2) & MatchAddrMode(3) & restriction) ~
|
||||||
|
(Elidable & HasOpcode(ldax) & HasImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(stax) & MatchParameter(4) & MatchAddrMode(5))
|
||||||
|
} else {
|
||||||
|
(Elidable & HasOpcode(ldax) & HasImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(stax) & MatchParameter(4) & MatchAddrMode(5)) ~
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchParameter(0) & MatchAddrMode(1)) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchParameter(2) & MatchAddrMode(3) & restriction)
|
||||||
|
}
|
||||||
|
val middle = (Linear & Not(ConcernsMemory) & DoesntChangeIndexingInAddrMode(3) & DoesntChangeIndexingInAddrMode(5)).*
|
||||||
|
val singleOriginalShift =
|
||||||
|
(Elidable & HasOpcode(ASL) & MatchParameter(2) & MatchAddrMode(3)) ~
|
||||||
|
(Elidable & HasOpcode(ROL) & MatchParameter(4) & MatchAddrMode(5) & DoesntMatterWhatItDoesWith(State.C, State.N, State.V, State.Z))
|
||||||
|
val originalShifting = (1 to i).map(_ => singleOriginalShift).reduce(_ ~ _)
|
||||||
|
originalStart ~ middle.capture(6) ~ originalShifting ~~> { (code, ctx) =>
|
||||||
|
val newStart = List(
|
||||||
|
code(0),
|
||||||
|
code(1).copy(addrMode = code(3).addrMode, parameter = code(3).parameter),
|
||||||
|
code(2),
|
||||||
|
code(3).copy(addrMode = code(1).addrMode, parameter = code(1).parameter))
|
||||||
|
val middle = ctx.get[List[AssemblyLine]](6)
|
||||||
|
val singleNewShift = List(
|
||||||
|
AssemblyLine(LSR, ctx.get[AddrMode.Value](5), ctx.get[Constant](4)),
|
||||||
|
AssemblyLine(ROR, ctx.get[AddrMode.Value](3), ctx.get[Constant](2)))
|
||||||
|
newStart ++ middle ++ (i until 8).flatMap(_ => singleNewShift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val SmarterShiftingWords = new RuleBasedAssemblyOptimization("Smarter shifting of words",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
wordShifting(8, hiFirst = false, hiFromX = true),
|
||||||
|
wordShifting(8, hiFirst = false, hiFromX = false),
|
||||||
|
wordShifting(8, hiFirst = true, hiFromX = true),
|
||||||
|
wordShifting(8, hiFirst = true, hiFromX = false),
|
||||||
|
wordShifting(7, hiFirst = false, hiFromX = true),
|
||||||
|
wordShifting(7, hiFirst = false, hiFromX = false),
|
||||||
|
wordShifting(7, hiFirst = true, hiFromX = true),
|
||||||
|
wordShifting(7, hiFirst = true, hiFromX = false),
|
||||||
|
wordShifting(6, hiFirst = false, hiFromX = true),
|
||||||
|
wordShifting(6, hiFirst = false, hiFromX = false),
|
||||||
|
wordShifting(6, hiFirst = true, hiFromX = true),
|
||||||
|
wordShifting(6, hiFirst = true, hiFromX = false),
|
||||||
|
wordShifting(5, hiFirst = false, hiFromX = true),
|
||||||
|
wordShifting(5, hiFirst = false, hiFromX = false),
|
||||||
|
wordShifting(5, hiFirst = true, hiFromX = true),
|
||||||
|
wordShifting(5, hiFirst = true, hiFromX = false),
|
||||||
|
)
|
||||||
|
|
||||||
|
private def carryFlagConversionCase(shift: Int, firstSet: Boolean, zeroIfSet: Boolean) = {
|
||||||
|
val nonZero = 1 << shift
|
||||||
|
val test = Elidable & HasOpcode(if (firstSet) BCC else BCS) & MatchParameter(0)
|
||||||
|
val ifSet = Elidable & HasOpcode(LDA) & HasImmediate(if (zeroIfSet) 0 else nonZero)
|
||||||
|
val ifClear = Elidable & HasOpcode(LDA) & HasImmediate(if (zeroIfSet) nonZero else 0)
|
||||||
|
val jump = Elidable & HasOpcodeIn(Set(JMP, if (firstSet) BCS else BCC, if (zeroIfSet) BEQ else BNE)) & MatchParameter(1)
|
||||||
|
val elseLabel = Elidable & HasOpcode(LABEL) & MatchParameter(0)
|
||||||
|
val afterLabel = Elidable & HasOpcode(LABEL) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.C, State.N, State.V, State.Z)
|
||||||
|
val store = Elidable & (Not(ReadsC) & Linear | HasOpcodeIn(Set(RTS, JSR, RTI)))
|
||||||
|
val secondReturn = (Elidable & HasOpcodeIn(Set(RTS, RTI) | NoopDiscardsFlags)).*.capture(6)
|
||||||
|
val where = Where { ctx =>
|
||||||
|
ctx.get[List[AssemblyLine]](4) == ctx.get[List[AssemblyLine]](5) ||
|
||||||
|
ctx.get[List[AssemblyLine]](4) == ctx.get[List[AssemblyLine]](5) ++ ctx.get[List[AssemblyLine]](6)
|
||||||
|
}
|
||||||
|
val pattern =
|
||||||
|
if (firstSet) test ~ ifSet ~ store.*.capture(4) ~ jump ~ elseLabel ~ ifClear ~ store.*.capture(5) ~ afterLabel ~ secondReturn ~ where
|
||||||
|
else test ~ ifClear ~ store.*.capture(4) ~ jump ~ elseLabel ~ ifSet ~ store.*.capture(5) ~ afterLabel ~ secondReturn ~ where
|
||||||
|
pattern ~~> { (_, ctx) =>
|
||||||
|
List(
|
||||||
|
AssemblyLine.immediate(LDA, 0),
|
||||||
|
AssemblyLine.implied(if (shift >= 4) ROR else ROL)) ++
|
||||||
|
(if (shift >= 4) List.fill(7 - shift)(AssemblyLine.implied(LSR)) else List.fill(shift)(AssemblyLine.implied(ASL))) ++
|
||||||
|
(if (zeroIfSet) List(AssemblyLine.immediate(EOR, nonZero)) else Nil) ++
|
||||||
|
ctx.get[List[AssemblyLine]](5) ++
|
||||||
|
ctx.get[List[AssemblyLine]](6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val CarryFlagConversion = new RuleBasedAssemblyOptimization("Carry flag conversion",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
// TODO: These yield 2 cycles more but 1–2 bytes less
|
||||||
|
// TODO: Add an "optimize for size" compilation option?
|
||||||
|
// carryFlagConversionCase(2, firstSet = false, zeroIfSet = false),
|
||||||
|
// carryFlagConversionCase(2, firstSet = true, zeroIfSet = false),
|
||||||
|
// carryFlagConversionCase(1, firstSet = true, zeroIfSet = true),
|
||||||
|
// carryFlagConversionCase(1, firstSet = false, zeroIfSet = true),
|
||||||
|
carryFlagConversionCase(1, firstSet = false, zeroIfSet = false),
|
||||||
|
carryFlagConversionCase(1, firstSet = true, zeroIfSet = false),
|
||||||
|
carryFlagConversionCase(0, firstSet = true, zeroIfSet = true),
|
||||||
|
carryFlagConversionCase(0, firstSet = false, zeroIfSet = true),
|
||||||
|
carryFlagConversionCase(0, firstSet = false, zeroIfSet = false),
|
||||||
|
carryFlagConversionCase(0, firstSet = true, zeroIfSet = false),
|
||||||
|
// carryFlagConversionCase(5, firstSet = false, zeroIfSet = false),
|
||||||
|
// carryFlagConversionCase(5, firstSet = true, zeroIfSet = false),
|
||||||
|
// carryFlagConversionCase(6, firstSet = true, zeroIfSet = true),
|
||||||
|
// carryFlagConversionCase(6, firstSet = false, zeroIfSet = true),
|
||||||
|
carryFlagConversionCase(6, firstSet = false, zeroIfSet = false),
|
||||||
|
carryFlagConversionCase(6, firstSet = true, zeroIfSet = false),
|
||||||
|
carryFlagConversionCase(7, firstSet = true, zeroIfSet = true),
|
||||||
|
carryFlagConversionCase(7, firstSet = false, zeroIfSet = true),
|
||||||
|
carryFlagConversionCase(7, firstSet = false, zeroIfSet = false),
|
||||||
|
carryFlagConversionCase(7, firstSet = true, zeroIfSet = false),
|
||||||
|
)
|
||||||
|
|
||||||
|
val Adc0Optimization = new RuleBasedAssemblyOptimization("ADC #0/#1 optimization",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BothFlows,
|
||||||
|
(Elidable & HasOpcode(LDA) & HasImmediate(0) & HasClear(State.D)) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
val label = getNextLabel("ah")
|
||||||
|
List(
|
||||||
|
AssemblyLine.relative(BCC, label),
|
||||||
|
code.last.copy(opcode = INC),
|
||||||
|
AssemblyLine.label(label))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & HasImmediate(0) & HasClear(State.D)) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
val label = getNextLabel("ah")
|
||||||
|
List(
|
||||||
|
AssemblyLine.relative(BCC, label),
|
||||||
|
code.last.copy(opcode = INC),
|
||||||
|
AssemblyLine.label(label))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDA) & HasImmediate(1) & HasClear(State.D) & HasClear(State.C)) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(code.last.copy(opcode = INC))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(1) & HasClear(State.C) & MatchParameter(2) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & HasImmediate(1) & HasClear(State.D)) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(code.last.copy(opcode = INC))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(TXA) & HasClear(State.D)) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & HasImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
val label = getNextLabel("ah")
|
||||||
|
List(
|
||||||
|
AssemblyLine.relative(BCC, label),
|
||||||
|
AssemblyLine.implied(INX),
|
||||||
|
AssemblyLine.label(label))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(TYA) & HasClear(State.D)) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & HasImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(TAY) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
val label = getNextLabel("ah")
|
||||||
|
List(
|
||||||
|
AssemblyLine.relative(BCC, label),
|
||||||
|
AssemblyLine.implied(INY),
|
||||||
|
AssemblyLine.label(label))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(TXA) & HasClear(State.D) & HasClear(State.C)) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & HasImmediate(1)) ~
|
||||||
|
(Elidable & HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(AssemblyLine.implied(INX))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(TYA) & HasClear(State.D) & HasClear(State.C)) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & HasImmediate(1)) ~
|
||||||
|
(Elidable & HasOpcode(TAY) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(AssemblyLine.implied(INY))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val IndexSequenceOptimization = new RuleBasedAssemblyOptimization("Index sequence optimization",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
|
||||||
|
(Elidable & HasOpcode(LDY) & MatchImmediate(1) & MatchY(0)) ~
|
||||||
|
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0))) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcode(LDY) & MatchImmediate(1) & MatchY(0)) ~
|
||||||
|
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0)+1)) ~~> (_ => List(AssemblyLine.implied(INY))),
|
||||||
|
(Elidable & HasOpcode(LDY) & MatchImmediate(1) & MatchY(0)) ~
|
||||||
|
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0)-1)) ~~> (_ => List(AssemblyLine.implied(DEY))),
|
||||||
|
(Elidable & HasOpcode(LDX) & MatchImmediate(1) & MatchX(0)) ~
|
||||||
|
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0))) ~~> (_ => Nil),
|
||||||
|
(Elidable & HasOpcode(LDX) & MatchImmediate(1) & MatchX(0)) ~
|
||||||
|
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0)+1)) ~~> (_ => List(AssemblyLine.implied(INX))),
|
||||||
|
(Elidable & HasOpcode(LDX) & MatchImmediate(1) & MatchX(0)) ~
|
||||||
|
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0)-1)) ~~> (_ => List(AssemblyLine.implied(DEX))),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.CompilationOptions
|
||||||
|
import millfork.assembly.AssemblyLine
|
||||||
|
import millfork.env.NormalFunction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
trait AssemblyOptimization {
|
||||||
|
def name: String
|
||||||
|
|
||||||
|
def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine]
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.CompilationOptions
|
||||||
|
import millfork.assembly.{AssemblyLine, OpcodeClasses}
|
||||||
|
import millfork.env.NormalFunction
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
object ChangeIndexRegisterOptimizationPreferringX2Y extends ChangeIndexRegisterOptimization(true)
|
||||||
|
object ChangeIndexRegisterOptimizationPreferringY2X extends ChangeIndexRegisterOptimization(false)
|
||||||
|
|
||||||
|
class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimization {
|
||||||
|
|
||||||
|
object IndexReg extends Enumeration {
|
||||||
|
val X, Y = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
object IndexDirection extends Enumeration {
|
||||||
|
val X2Y, Y2X = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
import IndexReg._
|
||||||
|
import IndexDirection._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
|
||||||
|
type IndexReg = IndexReg.Value
|
||||||
|
type IndexDirection = IndexDirection.Value
|
||||||
|
|
||||||
|
override def name = "Changing index registers"
|
||||||
|
|
||||||
|
override def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = {
|
||||||
|
val usesIndex = code.exists(l =>
|
||||||
|
OpcodeClasses.ReadsXAlways(l.opcode) ||
|
||||||
|
OpcodeClasses.ReadsYAlways(l.opcode) ||
|
||||||
|
OpcodeClasses.ChangesX(l.opcode) ||
|
||||||
|
OpcodeClasses.ChangesY(l.opcode) ||
|
||||||
|
Set(AbsoluteX, AbsoluteY, ZeroPageY, ZeroPageX, IndexedX, IndexedY)(l.addrMode)
|
||||||
|
)
|
||||||
|
if (!usesIndex) {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
val canX2Y = f.returnType.size <= 1 && canOptimize(code, X2Y, None)
|
||||||
|
val canY2X = canOptimize(code, Y2X, None)
|
||||||
|
(canX2Y, canY2X) match {
|
||||||
|
case (false, false) => code
|
||||||
|
case (true, false) =>
|
||||||
|
ErrorReporting.debug("Changing index register from X to Y")
|
||||||
|
switchX2Y(code)
|
||||||
|
case (false, true) =>
|
||||||
|
ErrorReporting.debug("Changing index register from X to Y")
|
||||||
|
switchY2X(code)
|
||||||
|
case (true, true) =>
|
||||||
|
if (preferX2Y) {
|
||||||
|
ErrorReporting.debug("Changing index register from X to Y (arbitrarily)")
|
||||||
|
switchX2Y(code)
|
||||||
|
} else {
|
||||||
|
ErrorReporting.debug("Changing index register from Y to X (arbitrarily)")
|
||||||
|
switchY2X(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection OptionEqualsSome
|
||||||
|
private def canOptimize(code: List[AssemblyLine], dir: IndexDirection, loaded: Option[IndexReg]): Boolean = code match {
|
||||||
|
case AssemblyLine(_, AbsoluteY, _, _) :: xs if loaded != Some(Y) => false
|
||||||
|
case AssemblyLine(_, ZeroPageY, _, _) :: xs if loaded != Some(Y) => false
|
||||||
|
case AssemblyLine(_, IndexedX, _, _) :: xs if dir == X2Y || loaded != Some(Y) => false
|
||||||
|
case AssemblyLine(_, AbsoluteX, _, _) :: xs if loaded != Some(X) => false
|
||||||
|
case AssemblyLine(_, ZeroPageX, _, _) :: xs if loaded != Some(X) => false
|
||||||
|
case AssemblyLine(_, IndexedY, _, _) :: xs if dir == Y2X || loaded != Some(Y) => false
|
||||||
|
|
||||||
|
// using a wrong index register for one instruction is fine
|
||||||
|
case AssemblyLine(LDY | TAY, _, _, _) :: AssemblyLine(_, IndexedY, _, _) :: xs if dir == Y2X =>
|
||||||
|
canOptimize(xs, dir, None)
|
||||||
|
case AssemblyLine(LDX | TAX, _, _, _) :: AssemblyLine(_, IndexedX, _, _) :: xs if dir == X2Y =>
|
||||||
|
canOptimize(xs, dir, None)
|
||||||
|
case AssemblyLine(LDX | TAX, _, _, _) :: AssemblyLine(INC | DEC | ASL | ROL | ROR | LSR | STZ, AbsoluteX | ZeroPageX, _, _) :: xs if dir == X2Y =>
|
||||||
|
canOptimize(xs, dir, None)
|
||||||
|
|
||||||
|
case AssemblyLine(INC | DEC | ASL | ROL | ROR | LSR | STZ, AbsoluteX | ZeroPageX, _, _) :: xs if dir == X2Y => false
|
||||||
|
|
||||||
|
case AssemblyLine(LAX, _, _, _) :: xs => false
|
||||||
|
case AssemblyLine(JSR, _, _, _) :: xs => false // TODO
|
||||||
|
case AssemblyLine(JMP, _, _, _) :: xs => canOptimize(xs, dir, None)
|
||||||
|
case AssemblyLine(op, _, _, _) :: xs if OpcodeClasses.ShortBranching(op) => canOptimize(xs, dir, None)
|
||||||
|
case AssemblyLine(RTS, _, _, _) :: xs => canOptimize(xs, dir, None)
|
||||||
|
case AssemblyLine(LABEL, _, _, _) :: xs => canOptimize(xs, dir, None)
|
||||||
|
case AssemblyLine(DISCARD_XF, _, _, _) :: xs => canOptimize(xs, dir, loaded.filter(_ != X))
|
||||||
|
case AssemblyLine(DISCARD_YF, _, _, _) :: xs => canOptimize(xs, dir, loaded.filter(_ != Y))
|
||||||
|
case AssemblyLine(_, DoesNotExist, _, _) :: xs => canOptimize(xs, dir, loaded)
|
||||||
|
|
||||||
|
case AssemblyLine(TAX | LDX | PLX, _, _, e) :: xs =>
|
||||||
|
(e || dir == Y2X) && canOptimize(xs, dir, Some(X))
|
||||||
|
case AssemblyLine(TAY | LDY | PLY, _, _, e) :: xs =>
|
||||||
|
(e || dir == X2Y) && canOptimize(xs, dir, Some(Y))
|
||||||
|
case AssemblyLine(TXA | STX | PHX | CPX | INX | DEX, _, _, e) :: xs =>
|
||||||
|
(e || dir == Y2X) && loaded == Some(X) && canOptimize(xs, dir, Some(X))
|
||||||
|
case AssemblyLine(TYA | STY | PHY | CPY | INY | DEY, _, _, e) :: xs =>
|
||||||
|
(e || dir == X2Y) && loaded == Some(Y) && canOptimize(xs, dir, Some(Y))
|
||||||
|
|
||||||
|
case AssemblyLine(SAX | TXS | SBX, _, _, _) :: xs => dir == Y2X && loaded == Some(X) && canOptimize(xs, dir, Some(X))
|
||||||
|
case AssemblyLine(TSX, _, _, _) :: xs => dir == Y2X && loaded != Some(Y) && canOptimize(xs, dir, Some(X))
|
||||||
|
|
||||||
|
case _ :: xs => canOptimize(xs, dir, loaded)
|
||||||
|
|
||||||
|
case Nil => true
|
||||||
|
}
|
||||||
|
|
||||||
|
private def switchX2Y(code: List[AssemblyLine]): List[AssemblyLine] = code match {
|
||||||
|
case (a@AssemblyLine(LDX | TAX, _, _, _)) :: (b@AssemblyLine(INC | DEC | ASL | ROL | ROR | LSR | STZ, AbsoluteX | ZeroPageX, _, _)) :: xs => a :: b :: switchX2Y(xs)
|
||||||
|
case (a@AssemblyLine(LDX | TAX, _, _, _)) :: (b@AssemblyLine(_, IndexedX, _, _)) :: xs => a :: b :: switchX2Y(xs)
|
||||||
|
case (x@AssemblyLine(TAX, _, _, _)) :: xs => x.copy(opcode = TAY) :: switchX2Y(xs)
|
||||||
|
case (x@AssemblyLine(TXA, _, _, _)) :: xs => x.copy(opcode = TYA) :: switchX2Y(xs)
|
||||||
|
case (x@AssemblyLine(STX, _, _, _)) :: xs => x.copy(opcode = STY) :: switchX2Y(xs)
|
||||||
|
case (x@AssemblyLine(LDX, _, _, _)) :: xs => x.copy(opcode = LDY) :: switchX2Y(xs)
|
||||||
|
case (x@AssemblyLine(INX, _, _, _)) :: xs => x.copy(opcode = INY) :: switchX2Y(xs)
|
||||||
|
case (x@AssemblyLine(DEX, _, _, _)) :: xs => x.copy(opcode = DEY) :: switchX2Y(xs)
|
||||||
|
case (x@AssemblyLine(CPX, _, _, _)) :: xs => x.copy(opcode = CPY) :: switchX2Y(xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LAX, _, _, _) :: xs => ErrorReporting.fatal("Unexpected LAX")
|
||||||
|
case AssemblyLine(TXS, _, _, _) :: xs => ErrorReporting.fatal("Unexpected TXS")
|
||||||
|
case AssemblyLine(TSX, _, _, _) :: xs => ErrorReporting.fatal("Unexpected TSX")
|
||||||
|
case AssemblyLine(SBX, _, _, _) :: xs => ErrorReporting.fatal("Unexpected SBX")
|
||||||
|
case AssemblyLine(SAX, _, _, _) :: xs => ErrorReporting.fatal("Unexpected SAX")
|
||||||
|
|
||||||
|
case (x@AssemblyLine(_, AbsoluteX, _, _)) :: xs => x.copy(addrMode = AbsoluteY) :: switchX2Y(xs)
|
||||||
|
case (x@AssemblyLine(_, ZeroPageX, _, _)) :: xs => x.copy(addrMode = ZeroPageY) :: switchX2Y(xs)
|
||||||
|
case (x@AssemblyLine(_, IndexedX, _, _)) :: xs => ErrorReporting.fatal("Unexpected IndexedX")
|
||||||
|
|
||||||
|
case x::xs => x :: switchX2Y(xs)
|
||||||
|
case Nil => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private def switchY2X(code: List[AssemblyLine]): List[AssemblyLine] = code match {
|
||||||
|
case AssemblyLine(LDY | TAY, _, _, _) :: AssemblyLine(_, IndexedY, _, _) :: xs => code.take(2) ++ switchY2X(xs)
|
||||||
|
case (x@AssemblyLine(TAY, _, _, _)) :: xs => x.copy(opcode = TAX) :: switchY2X(xs)
|
||||||
|
case (x@AssemblyLine(TYA, _, _, _)) :: xs => x.copy(opcode = TXA) :: switchY2X(xs)
|
||||||
|
case (x@AssemblyLine(STY, _, _, _)) :: xs => x.copy(opcode = STX) :: switchY2X(xs)
|
||||||
|
case (x@AssemblyLine(LDY, _, _, _)) :: xs => x.copy(opcode = LDX) :: switchY2X(xs)
|
||||||
|
case (x@AssemblyLine(INY, _, _, _)) :: xs => x.copy(opcode = INX) :: switchY2X(xs)
|
||||||
|
case (x@AssemblyLine(DEY, _, _, _)) :: xs => x.copy(opcode = DEX) :: switchY2X(xs)
|
||||||
|
case (x@AssemblyLine(CPY, _, _, _)) :: xs => x.copy(opcode = CPX) :: switchY2X(xs)
|
||||||
|
|
||||||
|
case (x@AssemblyLine(_, AbsoluteY, _, _)) :: xs => x.copy(addrMode = AbsoluteX) :: switchY2X(xs)
|
||||||
|
case (x@AssemblyLine(_, ZeroPageY, _, _)) :: xs => x.copy(addrMode = ZeroPageX) :: switchY2X(xs)
|
||||||
|
case AssemblyLine(_, IndexedY, _, _) :: xs => ErrorReporting.fatal("Unexpected IndexedY")
|
||||||
|
|
||||||
|
case x::xs => x :: switchY2X(xs)
|
||||||
|
case Nil => Nil
|
||||||
|
}
|
||||||
|
}
|
36
src/main/scala/millfork/assembly/opt/CmosOptimizations.scala
Normal file
36
src/main/scala/millfork/assembly/opt/CmosOptimizations.scala
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.assembly.{AssemblyLine, Opcode}
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.assembly.OpcodeClasses._
|
||||||
|
import millfork.env.{Constant, NormalFunction}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object CmosOptimizations {
|
||||||
|
|
||||||
|
val StzAddrModes = Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX)
|
||||||
|
|
||||||
|
val ZeroStoreAsStz = new RuleBasedAssemblyOptimization("Zero store",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
|
||||||
|
(HasA(0) & HasOpcode(STA) & Elidable & HasAddrModeIn(StzAddrModes)) ~~> {code =>
|
||||||
|
code.head.copy(opcode = STZ) :: Nil
|
||||||
|
},
|
||||||
|
(HasX(0) & HasOpcode(STX) & Elidable & HasAddrModeIn(StzAddrModes)) ~~> {code =>
|
||||||
|
code.head.copy(opcode = STZ) :: Nil
|
||||||
|
},
|
||||||
|
(HasY(0) & HasOpcode(STY) & Elidable & HasAddrModeIn(StzAddrModes)) ~~> {code =>
|
||||||
|
code.head.copy(opcode = STZ) :: Nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val OptimizeZeroIndex = new RuleBasedAssemblyOptimization("Optimizing zero index",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
|
||||||
|
(Elidable & HasY(0) & HasAddrMode(IndexedY) & HasOpcodeIn(SupportsZeroPageIndirect)) ~~> (code => code.map(_.copy(addrMode = ZeroPageIndirect))),
|
||||||
|
(Elidable & HasX(0) & HasAddrMode(IndexedX) & HasOpcodeIn(SupportsZeroPageIndirect)) ~~> (code => code.map(_.copy(addrMode = ZeroPageIndirect))),
|
||||||
|
)
|
||||||
|
|
||||||
|
val All: List[AssemblyOptimization] = List(ZeroStoreAsStz)
|
||||||
|
}
|
259
src/main/scala/millfork/assembly/opt/CoarseFlowAnalyzer.scala
Normal file
259
src/main/scala/millfork/assembly/opt/CoarseFlowAnalyzer.scala
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.assembly.{AssemblyLine, OpcodeClasses, State}
|
||||||
|
import millfork.env.{Label, MemoryAddressConstant, NormalFunction, NumericConstant}
|
||||||
|
|
||||||
|
import scala.collection.immutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
sealed trait Status[T] {
|
||||||
|
def contains(value: T): Boolean
|
||||||
|
|
||||||
|
def ~(that: Status[T]): Status[T] = {
|
||||||
|
(this, that) match {
|
||||||
|
case (AnyStatus(), _) => AnyStatus()
|
||||||
|
case (_, AnyStatus()) => AnyStatus()
|
||||||
|
case (SingleStatus(x), SingleStatus(y)) => if (x == y) SingleStatus(x) else AnyStatus()
|
||||||
|
case (SingleStatus(x), UnknownStatus()) => SingleStatus(x)
|
||||||
|
case (UnknownStatus(), SingleStatus(x)) => SingleStatus(x)
|
||||||
|
case (UnknownStatus(), UnknownStatus()) => UnknownStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object Status {
|
||||||
|
|
||||||
|
implicit class IntStatusOps(val inner: Status[Int]) extends AnyVal {
|
||||||
|
def map[T](f: Int => T): Status[T] = inner match {
|
||||||
|
case SingleStatus(x) => SingleStatus(f(x))
|
||||||
|
case _ => AnyStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
def z(f: Int => Int = identity): Status[Boolean] = inner match {
|
||||||
|
case SingleStatus(x) =>
|
||||||
|
val y = f(x) & 0xff
|
||||||
|
SingleStatus(y == 0)
|
||||||
|
case _ => AnyStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
def n(f: Int => Int = identity): Status[Boolean] = inner match {
|
||||||
|
case SingleStatus(x) =>
|
||||||
|
val y = f(x) & 0xff
|
||||||
|
SingleStatus(y >= 0x80)
|
||||||
|
case _ => AnyStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case class SingleStatus[T](t: T) extends Status[T] {
|
||||||
|
override def contains(value: T): Boolean = t == value
|
||||||
|
|
||||||
|
override def toString: String = t match {
|
||||||
|
case true => "1"
|
||||||
|
case false => "0"
|
||||||
|
case _ => t.toString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class UnknownStatus[T]() extends Status[T] {
|
||||||
|
override def contains(value: T) = false
|
||||||
|
|
||||||
|
override def toString: String = "_"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class AnyStatus[T]() extends Status[T] {
|
||||||
|
override def contains(value: T) = false
|
||||||
|
|
||||||
|
override def toString: String = "#"
|
||||||
|
}
|
||||||
|
//noinspection RedundantNewCaseClass
|
||||||
|
case class CpuStatus(a: Status[Int] = UnknownStatus(),
|
||||||
|
x: Status[Int] = UnknownStatus(),
|
||||||
|
y: Status[Int] = UnknownStatus(),
|
||||||
|
z: Status[Boolean] = UnknownStatus(),
|
||||||
|
n: Status[Boolean] = UnknownStatus(),
|
||||||
|
c: Status[Boolean] = UnknownStatus(),
|
||||||
|
v: Status[Boolean] = UnknownStatus(),
|
||||||
|
d: Status[Boolean] = UnknownStatus(),
|
||||||
|
) {
|
||||||
|
|
||||||
|
override def toString: String = s"A=$a,X=$x,Y=$y,Z=$z,N=$n,C=$c,V=$v,D=$d"
|
||||||
|
|
||||||
|
def nz: CpuStatus =
|
||||||
|
this.copy(n = AnyStatus(), z = AnyStatus())
|
||||||
|
|
||||||
|
def nz(i: Long): CpuStatus =
|
||||||
|
this.copy(n = SingleStatus((i & 0x80) != 0), z = SingleStatus((i & 0xff) == 0))
|
||||||
|
|
||||||
|
def ~(that: CpuStatus) = new CpuStatus(
|
||||||
|
a = this.a ~ that.a,
|
||||||
|
x = this.x ~ that.x,
|
||||||
|
y = this.y ~ that.y,
|
||||||
|
z = this.z ~ that.z,
|
||||||
|
n = this.n ~ that.n,
|
||||||
|
c = this.c ~ that.c,
|
||||||
|
v = this.v ~ that.v,
|
||||||
|
d = this.d ~ that.d,
|
||||||
|
)
|
||||||
|
|
||||||
|
def hasClear(state: State.Value): Boolean = state match {
|
||||||
|
case State.A => a.contains(0)
|
||||||
|
case State.X => x.contains(0)
|
||||||
|
case State.Y => y.contains(0)
|
||||||
|
case State.Z => z.contains(false)
|
||||||
|
case State.N => n.contains(false)
|
||||||
|
case State.C => c.contains(false)
|
||||||
|
case State.V => v.contains(false)
|
||||||
|
case State.D => d.contains(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
def hasSet(state: State.Value): Boolean = state match {
|
||||||
|
case State.A => false
|
||||||
|
case State.X => false
|
||||||
|
case State.Y => false
|
||||||
|
case State.Z => z.contains(true)
|
||||||
|
case State.N => n.contains(true)
|
||||||
|
case State.C => c.contains(true)
|
||||||
|
case State.V => v.contains(true)
|
||||||
|
case State.D => d.contains(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object CoarseFlowAnalyzer {
|
||||||
|
//noinspection RedundantNewCaseClass
|
||||||
|
def analyze(f: NormalFunction, code: List[AssemblyLine]): List[CpuStatus] = {
|
||||||
|
val flagArray = Array.fill[CpuStatus](code.length)(CpuStatus())
|
||||||
|
val codeArray = code.toArray
|
||||||
|
val initialStatus = new CpuStatus(d = SingleStatus(false))
|
||||||
|
|
||||||
|
var changed = true
|
||||||
|
while (changed) {
|
||||||
|
changed = false
|
||||||
|
var currentStatus: CpuStatus = if (f.interrupt) CpuStatus() else initialStatus
|
||||||
|
for (i <- codeArray.indices) {
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
if (flagArray(i) != currentStatus) {
|
||||||
|
changed = true
|
||||||
|
flagArray(i) = currentStatus
|
||||||
|
}
|
||||||
|
codeArray(i) match {
|
||||||
|
case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(l)), _) =>
|
||||||
|
val L = l
|
||||||
|
currentStatus = codeArray.indices.flatMap(j => codeArray(j) match {
|
||||||
|
case AssemblyLine(_, _, MemoryAddressConstant(Label(L)), _) => Some(flagArray(j))
|
||||||
|
case _ => None
|
||||||
|
}).fold(CpuStatus())(_ ~ _)
|
||||||
|
|
||||||
|
case AssemblyLine(BCC, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(c = currentStatus.c ~ SingleStatus(true))
|
||||||
|
case AssemblyLine(BCS, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(c = currentStatus.c ~ SingleStatus(false))
|
||||||
|
case AssemblyLine(BVS, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(v = currentStatus.v ~ SingleStatus(false))
|
||||||
|
case AssemblyLine(BVC, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(v = currentStatus.v ~ SingleStatus(true))
|
||||||
|
case AssemblyLine(BMI, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.n ~ SingleStatus(false))
|
||||||
|
case AssemblyLine(BPL, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.n ~ SingleStatus(true))
|
||||||
|
case AssemblyLine(BEQ, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(z = currentStatus.z ~ SingleStatus(false))
|
||||||
|
case AssemblyLine(BNE, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(z = currentStatus.z ~ SingleStatus(true))
|
||||||
|
|
||||||
|
case AssemblyLine(SED, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(d = SingleStatus(true))
|
||||||
|
case AssemblyLine(SEC, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(c = SingleStatus(true))
|
||||||
|
case AssemblyLine(CLD, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(d = SingleStatus(false))
|
||||||
|
case AssemblyLine(CLC, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(c = SingleStatus(false))
|
||||||
|
case AssemblyLine(CLV, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(v = SingleStatus(false))
|
||||||
|
|
||||||
|
case AssemblyLine(JSR, _, _, _) =>
|
||||||
|
currentStatus = initialStatus
|
||||||
|
|
||||||
|
case AssemblyLine(LDX, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn.toInt
|
||||||
|
currentStatus = currentStatus.nz(n).copy(x = SingleStatus(n))
|
||||||
|
case AssemblyLine(LDY, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn.toInt
|
||||||
|
currentStatus = currentStatus.nz(n).copy(y = SingleStatus(n))
|
||||||
|
case AssemblyLine(LDA, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn.toInt
|
||||||
|
currentStatus = currentStatus.nz(n).copy(a = SingleStatus(n))
|
||||||
|
case AssemblyLine(LAX, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn.toInt
|
||||||
|
currentStatus = currentStatus.nz(n).copy(a = SingleStatus(n), x = SingleStatus(n))
|
||||||
|
|
||||||
|
case AssemblyLine(EOR, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn.toInt
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.a.n(_ ^ n), z = currentStatus.a.z(_ ^ n), a = currentStatus.a.map(_ ^ n))
|
||||||
|
case AssemblyLine(AND, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn.toInt
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.a.n(_ & n), z = currentStatus.a.z(_ & n), a = currentStatus.a.map(_ & n))
|
||||||
|
case AssemblyLine(ANC, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn.toInt
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.a.n(_ & n), c = currentStatus.a.n(_ & n), z = currentStatus.x.z(_ & n), a = currentStatus.a.map(_ & n))
|
||||||
|
case AssemblyLine(ORA, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn.toInt
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.a.n(_ | n), z = currentStatus.a.z(_ | n), a = currentStatus.a.map(_ | n))
|
||||||
|
case AssemblyLine(ALR, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn.toInt
|
||||||
|
currentStatus = currentStatus.copy(
|
||||||
|
n = currentStatus.a.n(i => (i & n & 0xff) >> 1),
|
||||||
|
z = currentStatus.a.z(i => (i & n & 0xff) >> 1),
|
||||||
|
c = currentStatus.a.map(i => (i & n & 1) == 0),
|
||||||
|
a = currentStatus.a.map(i => (i & n & 0xff) >> 1))
|
||||||
|
|
||||||
|
case AssemblyLine(INX, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.x.n(_ + 1), z = currentStatus.x.z(_ + 1), x = currentStatus.x.map(_ + 1))
|
||||||
|
case AssemblyLine(DEX, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.x.n(_ - 1), z = currentStatus.x.z(_ - 1), x = currentStatus.x.map(_ - 1))
|
||||||
|
case AssemblyLine(INY, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.y.n(_ + 1), z = currentStatus.y.z(_ + 1), y = currentStatus.y.map(_ + 1))
|
||||||
|
case AssemblyLine(DEY, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(n = currentStatus.y.n(_ - 1), z = currentStatus.y.z(_ - 1), y = currentStatus.y.map(_ - 1))
|
||||||
|
case AssemblyLine(TAX, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(x = currentStatus.a, n = currentStatus.a.n(), z = currentStatus.a.z())
|
||||||
|
case AssemblyLine(TXA, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(a = currentStatus.x, n = currentStatus.x.n(), z = currentStatus.x.z())
|
||||||
|
case AssemblyLine(TAY, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(y = currentStatus.a, n = currentStatus.a.n(), z = currentStatus.a.z())
|
||||||
|
case AssemblyLine(TYA, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.copy(a = currentStatus.y, n = currentStatus.y.n(), z = currentStatus.y.z())
|
||||||
|
|
||||||
|
case AssemblyLine(opcode, addrMode, parameter, _) =>
|
||||||
|
if (OpcodeClasses.ChangesX(opcode)) currentStatus = currentStatus.copy(x = AnyStatus())
|
||||||
|
if (OpcodeClasses.ChangesY(opcode)) currentStatus = currentStatus.copy(y = AnyStatus())
|
||||||
|
if (OpcodeClasses.ChangesAAlways(opcode)) currentStatus = currentStatus.copy(a = AnyStatus())
|
||||||
|
if (addrMode == Implied && OpcodeClasses.ChangesAIfImplied(opcode)) currentStatus = currentStatus.copy(a = AnyStatus())
|
||||||
|
if (OpcodeClasses.ChangesNAndZ(opcode)) currentStatus = currentStatus.nz
|
||||||
|
if (OpcodeClasses.ChangesC(opcode)) currentStatus = currentStatus.copy(c = AnyStatus())
|
||||||
|
if (OpcodeClasses.ChangesV(opcode)) currentStatus = currentStatus.copy(v = AnyStatus())
|
||||||
|
if (opcode == CMP || opcode == CPX || opcode == CPY) {
|
||||||
|
if (addrMode == Immediate) parameter match {
|
||||||
|
case NumericConstant(0, _) => currentStatus = currentStatus.copy(c = SingleStatus(true))
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// flagArray.zip(codeArray).foreach{
|
||||||
|
// case (fl, y) => if (y.isPrintable) println(f"$fl%-32s $y%-32s")
|
||||||
|
// }
|
||||||
|
// println("---------------------")
|
||||||
|
}
|
||||||
|
|
||||||
|
flagArray.toList
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.assembly._
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.env._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object DangerousOptimizations {
|
||||||
|
|
||||||
|
val ConstantIndexOffsetPropagation = new RuleBasedAssemblyOptimization("Constant index offset propagation",
|
||||||
|
// TODO: try to guess when overflow can happen
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BothFlows,
|
||||||
|
(Elidable & HasOpcode(CLC)).? ~
|
||||||
|
(Elidable & HasClear(State.C) & HasOpcode(ADC) & MatchImmediate(0) & DoesntMatterWhatItDoesWith(State.V, State.C)) ~
|
||||||
|
(
|
||||||
|
(HasOpcode(TAY) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.A)) ~
|
||||||
|
(Linear & Not(ConcernsY)).*
|
||||||
|
).capture(1) ~
|
||||||
|
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
|
||||||
|
val last = code.last
|
||||||
|
ctx.get[List[AssemblyLine]](1) :+ last.copy(parameter = last.parameter.+(ctx.get[Constant](0)).quickSimplify)
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(CLC)).? ~
|
||||||
|
(Elidable & HasClear(State.C) & HasOpcode(ADC) & MatchImmediate(0) & DoesntMatterWhatItDoesWith(State.V, State.C)) ~
|
||||||
|
(
|
||||||
|
(HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.A)) ~
|
||||||
|
(Linear & Not(ConcernsX)).*
|
||||||
|
).capture(1) ~
|
||||||
|
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
|
||||||
|
val last = code.last
|
||||||
|
ctx.get[List[AssemblyLine]](1) :+ last.copy(parameter = last.parameter.+(ctx.get[Constant](0)).quickSimplify)
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(INY) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
|
||||||
|
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
|
||||||
|
val last = code.last
|
||||||
|
List(last.copy(parameter = last.parameter.+(1).quickSimplify))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(DEY) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
|
||||||
|
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
|
||||||
|
val last = code.last
|
||||||
|
List(last.copy(parameter = last.parameter.+(-1).quickSimplify))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(INX) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
|
||||||
|
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
|
||||||
|
val last = code.last
|
||||||
|
List(last.copy(parameter = last.parameter.+(1).quickSimplify))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
|
||||||
|
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
|
||||||
|
val last = code.last
|
||||||
|
List(last.copy(parameter = last.parameter.+(-1).quickSimplify))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val All: List[AssemblyOptimization] = List(ConstantIndexOffsetPropagation)
|
||||||
|
}
|
34
src/main/scala/millfork/assembly/opt/FlowAnalyzer.scala
Normal file
34
src/main/scala/millfork/assembly/opt/FlowAnalyzer.scala
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.{CompilationFlag, CompilationOptions}
|
||||||
|
import millfork.assembly.{AssemblyLine, State}
|
||||||
|
import millfork.env.NormalFunction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
case class FlowInfo(statusBefore: CpuStatus, importanceAfter: CpuImportance) {
|
||||||
|
|
||||||
|
def hasClear(state: State.Value): Boolean = statusBefore.hasClear(state)
|
||||||
|
|
||||||
|
def hasSet(state: State.Value): Boolean = statusBefore.hasSet(state)
|
||||||
|
|
||||||
|
def isUnimportant(state: State.Value): Boolean = importanceAfter.isUnimportant(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
object FlowInfo {
|
||||||
|
val Default = FlowInfo(CpuStatus(), CpuImportance())
|
||||||
|
}
|
||||||
|
|
||||||
|
object FlowAnalyzer {
|
||||||
|
def analyze(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[(FlowInfo, AssemblyLine)] = {
|
||||||
|
val forwardFlow = if (options.flag(CompilationFlag.DetailedFlowAnalysis)) {
|
||||||
|
QuantumFlowAnalyzer.analyze(f, code).map(_.collapse)
|
||||||
|
} else {
|
||||||
|
CoarseFlowAnalyzer.analyze(f, code)
|
||||||
|
}
|
||||||
|
val reverseFlow = ReverseFlowAnalyzer.analyze(f, code)
|
||||||
|
forwardFlow.zip(reverseFlow).map{case (s,i) => FlowInfo(s,i)}.zip(code)
|
||||||
|
}
|
||||||
|
}
|
242
src/main/scala/millfork/assembly/opt/LaterOptimizations.scala
Normal file
242
src/main/scala/millfork/assembly/opt/LaterOptimizations.scala
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.assembly.{AddrMode, AssemblyLine, Opcode, State}
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.assembly.OpcodeClasses._
|
||||||
|
import millfork.env.{Constant, NormalFunction, NumericConstant}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These optimizations help on their own, but may prevent other optimizations from triggering.
|
||||||
|
*
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object LaterOptimizations {
|
||||||
|
|
||||||
|
|
||||||
|
// This optimization tends to prevent later Variable To Register Optimization,
|
||||||
|
// so run this only after it's pretty sure V2RO won't happen any more
|
||||||
|
val DoubleLoadToDifferentRegisters = new RuleBasedAssemblyOptimization("Double load to different registers",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
TwoDifferentLoadsWithNoFlagChangeInBetween(LDA, Not(ChangesA), LDX, TAX),
|
||||||
|
TwoDifferentLoadsWithNoFlagChangeInBetween(LDA, Not(ChangesA), LDY, TAY),
|
||||||
|
TwoDifferentLoadsWithNoFlagChangeInBetween(LDX, Not(ChangesX), LDA, TXA),
|
||||||
|
TwoDifferentLoadsWithNoFlagChangeInBetween(LDY, Not(ChangesY), LDA, TYA),
|
||||||
|
TwoDifferentLoadsWhoseFlagsWillNotBeChecked(LDA, Not(ChangesA), LDX, TAX),
|
||||||
|
TwoDifferentLoadsWhoseFlagsWillNotBeChecked(LDA, Not(ChangesA), LDY, TAY),
|
||||||
|
TwoDifferentLoadsWhoseFlagsWillNotBeChecked(LDX, Not(ChangesX), LDA, TXA),
|
||||||
|
TwoDifferentLoadsWhoseFlagsWillNotBeChecked(LDY, Not(ChangesY), LDA, TYA),
|
||||||
|
)
|
||||||
|
|
||||||
|
private def TwoDifferentLoadsWithNoFlagChangeInBetween(opcode1: Opcode.Value, middle: AssemblyLinePattern, opcode2: Opcode.Value, transferOpcode: Opcode.Value) = {
|
||||||
|
(HasOpcode(opcode1) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(LinearOrLabel & Not(ChangesMemory) & middle & Not(HasOpcode(opcode2))).* ~
|
||||||
|
(HasOpcode(opcode2) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~~> { c =>
|
||||||
|
c.init :+ AssemblyLine.implied(transferOpcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def TwoDifferentLoadsWhoseFlagsWillNotBeChecked(opcode1: Opcode.Value, middle: AssemblyLinePattern, opcode2: Opcode.Value, transferOpcode: Opcode.Value) = {
|
||||||
|
((HasOpcode(opcode1) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(LinearOrLabel & Not(ChangesMemory) & middle & Not(HasOpcode(opcode2))).*).capture(2) ~
|
||||||
|
(HasOpcode(opcode2) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
((LinearOrLabel & Not(ReadsNOrZ) & Not(ChangesNAndZ)).* ~ ChangesNAndZ).capture(3) ~~> { (_, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](2) ++ (AssemblyLine.implied(transferOpcode) :: ctx.get[List[AssemblyLine]](3))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def TwoIdenticalLoadsWithNoFlagChangeInBetween(opcode: Opcode.Value, middle: AssemblyLinePattern) = {
|
||||||
|
(HasOpcode(opcode) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(LinearOrLabel & Not(ChangesMemory) & middle & Not(ChangesNAndZ)).* ~
|
||||||
|
(HasOpcode(opcode) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~~> { c =>
|
||||||
|
c.init
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(opcode: Opcode.Value, middle: AssemblyLinePattern) = {
|
||||||
|
(HasOpcode(opcode) & HasAddrMode(Immediate) & MatchParameter(1)) ~
|
||||||
|
(LinearOrLabel & middle & Not(ChangesNAndZ)).* ~
|
||||||
|
(HasOpcode(opcode) & Elidable & HasAddrMode(Immediate) & MatchParameter(1)) ~~> { c =>
|
||||||
|
c.init
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(opcode: Opcode.Value, middle: AssemblyLinePattern) = {
|
||||||
|
((HasOpcode(opcode) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(LinearOrLabel & Not(ChangesMemory) & middle).*).capture(2) ~
|
||||||
|
(HasOpcode(opcode) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
((LinearOrLabel & Not(ReadsNOrZ) & Not(ChangesNAndZ)).* ~ ChangesNAndZ).capture(3) ~~> { (_, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](2) ++ ctx.get[List[AssemblyLine]](3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection ZeroIndexToHead
|
||||||
|
private def InterleavedImmediateLoads(load: Opcode.Value, store: Opcode.Value) = {
|
||||||
|
(Elidable & HasOpcode(load) & MatchImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(store) & HasAddrMode(Absolute) & MatchParameter(8)) ~
|
||||||
|
(Elidable & HasOpcode(load) & MatchImmediate(1)) ~
|
||||||
|
(Elidable & HasOpcode(store) & HasAddrMode(Absolute) & MatchParameter(9) & DontMatchParameter(8)) ~
|
||||||
|
(Elidable & HasOpcode(load) & MatchImmediate(0)) ~~> { c =>
|
||||||
|
List(c(2), c(3), c(0), c(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection ZeroIndexToHead
|
||||||
|
private def InterleavedAbsoluteLoads(load: Opcode.Value, store: Opcode.Value) = {
|
||||||
|
(Elidable & HasOpcode(load) & HasAddrMode(Absolute) & MatchParameter(0)) ~
|
||||||
|
(Elidable & HasOpcode(store) & HasAddrMode(Absolute) & MatchParameter(8) & DontMatchParameter(0)) ~
|
||||||
|
(Elidable & HasOpcode(load) & HasAddrMode(Absolute) & MatchParameter(1) & DontMatchParameter(8) & DontMatchParameter(0)) ~
|
||||||
|
(Elidable & HasOpcode(store) & HasAddrMode(Absolute) & MatchParameter(9) & DontMatchParameter(8) & DontMatchParameter(1) & DontMatchParameter(0)) ~
|
||||||
|
(Elidable & HasOpcode(load) & HasAddrMode(Absolute) & MatchParameter(0)) ~~> { c =>
|
||||||
|
List(c(2), c(3), c(0), c(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val DoubleLoadToTheSameRegister = new RuleBasedAssemblyOptimization("Double load to the same register",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
TwoIdenticalLoadsWithNoFlagChangeInBetween(LDA, Not(ChangesA)),
|
||||||
|
TwoIdenticalLoadsWithNoFlagChangeInBetween(LDX, Not(ChangesX)),
|
||||||
|
TwoIdenticalLoadsWithNoFlagChangeInBetween(LDY, Not(ChangesY)),
|
||||||
|
TwoIdenticalLoadsWithNoFlagChangeInBetween(LAX, Not(ChangesA) & Not(ChangesX)),
|
||||||
|
TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(LDA, Not(ChangesA)),
|
||||||
|
TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(LDX, Not(ChangesX)),
|
||||||
|
TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(LDY, Not(ChangesY)),
|
||||||
|
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LDA, Not(ChangesA)),
|
||||||
|
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LDX, Not(ChangesX)),
|
||||||
|
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LDY, Not(ChangesY)),
|
||||||
|
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LAX, Not(ChangesA) & Not(ChangesX)),
|
||||||
|
InterleavedImmediateLoads(LDA, STA),
|
||||||
|
InterleavedImmediateLoads(LDX, STX),
|
||||||
|
InterleavedImmediateLoads(LDY, STY),
|
||||||
|
InterleavedAbsoluteLoads(LDA, STA),
|
||||||
|
InterleavedAbsoluteLoads(LDX, STX),
|
||||||
|
InterleavedAbsoluteLoads(LDY, STY),
|
||||||
|
)
|
||||||
|
|
||||||
|
private def pointlessLoadAfterStore(store: Opcode.Value, load: Opcode.Value, addrMode: AddrMode.Value, meantime: AssemblyLinePattern = Anything) = {
|
||||||
|
((HasOpcode(store) & HasAddrMode(addrMode) & MatchParameter(1)) ~
|
||||||
|
(LinearOrBranch & Not(ChangesA) & Not(ChangesMemory) & meantime).*).capture(2) ~
|
||||||
|
(HasOpcode(load) & Elidable & HasAddrMode(addrMode) & MatchParameter(1)) ~
|
||||||
|
((LinearOrLabel & Not(ReadsNOrZ) & Not(ChangesNAndZ)).* ~ ChangesNAndZ).capture(3) ~~> { (_, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](2) ++ ctx.get[List[AssemblyLine]](3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val PointlessLoadAfterStore = new RuleBasedAssemblyOptimization("Pointless load after store",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
pointlessLoadAfterStore(STA, LDA, Absolute),
|
||||||
|
pointlessLoadAfterStore(STA, LDA, AbsoluteX, Not(ChangesX)),
|
||||||
|
pointlessLoadAfterStore(STA, LDA, AbsoluteY, Not(ChangesY)),
|
||||||
|
pointlessLoadAfterStore(STX, LDX, Absolute),
|
||||||
|
pointlessLoadAfterStore(STY, LDY, Absolute),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
private val ShiftAddrModes = Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX)
|
||||||
|
private val ShiftOpcodes = Set(ASL, ROL, ROR, LSR)
|
||||||
|
|
||||||
|
// LDA-SHIFT-STA is slower than just SHIFT
|
||||||
|
// LDA-SHIFT-SHIFT-STA is equally fast as SHIFT-SHIFT, but the latter doesn't use the accumulator
|
||||||
|
val PointessLoadingForShifting = new RuleBasedAssemblyOptimization("Pointless loading for shifting",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasOpcode(LDA) & HasAddrModeIn(ShiftAddrModes) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Elidable & HasOpcodeIn(ShiftOpcodes) & HasAddrMode(Implied) & MatchOpcode(2)) ~
|
||||||
|
(Elidable & HasOpcode(STA) & HasAddrModeIn(ShiftAddrModes) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Not(ReadsA) & Not(OverwritesA)).* ~ OverwritesA ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine(ctx.get[Opcode.Value](2), ctx.get[AddrMode.Value](0), ctx.get[Constant](1)) :: code.drop(3)
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDA) & HasAddrModeIn(ShiftAddrModes) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Elidable & HasOpcodeIn(ShiftOpcodes) & HasAddrMode(Implied) & MatchOpcode(2)) ~
|
||||||
|
(Elidable & HasOpcodeIn(ShiftOpcodes) & HasAddrMode(Implied) & MatchOpcode(2)) ~
|
||||||
|
(Elidable & HasOpcode(STA) & HasAddrModeIn(ShiftAddrModes) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Not(ReadsA) & Not(OverwritesA)).* ~ OverwritesA ~~> { (code, ctx) =>
|
||||||
|
val shift = AssemblyLine(ctx.get[Opcode.Value](2), ctx.get[AddrMode.Value](0), ctx.get[Constant](1))
|
||||||
|
shift :: shift :: code.drop(4)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// SHIFT-LDA is equally fast as LDA-SHIFT-STA, but can enable further optimizations doesn't use the accumulator
|
||||||
|
// LDA-SHIFT-SHIFT-STA is equally fast as SHIFT-SHIFT, but the latter doesn't use the accumulator
|
||||||
|
val LoadingAfterShifting = new RuleBasedAssemblyOptimization("Loading after shifting",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasOpcodeIn(ShiftOpcodes) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
|
||||||
|
AssemblyLine(LDA, ctx.get[AddrMode.Value](0), ctx.get[Constant](1)) ::
|
||||||
|
AssemblyLine.implied(code.head.opcode) ::
|
||||||
|
AssemblyLine(STA, ctx.get[AddrMode.Value](0), ctx.get[Constant](1)) ::
|
||||||
|
code.drop(2)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseZeropageAddressingMode = new RuleBasedAssemblyOptimization("Using zeropage addressing mode",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasAddrMode(Absolute) & MatchParameter(0)) ~ Where(ctx => ctx.get[Constant](0).quickSimplify match {
|
||||||
|
case NumericConstant(x, _) => (x & 0xff00) == 0
|
||||||
|
case _ => false
|
||||||
|
}) ~~> (code => code.head.copy(addrMode = ZeroPage) :: Nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseXInsteadOfStack = new RuleBasedAssemblyOptimization("Using X instead of stack",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
(Elidable & HasOpcode(PHA) & DoesntMatterWhatItDoesWith(State.X)) ~
|
||||||
|
(Not(ConcernsStack) & Not(ConcernsX)).capture(1) ~
|
||||||
|
Where(_.isExternallyLinearBlock(1)) ~
|
||||||
|
(Elidable & HasOpcode(PLA)) ~~> (c =>
|
||||||
|
AssemblyLine.implied(TAX) :: (c.tail.init :+ AssemblyLine.implied(TXA))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseYInsteadOfStack = new RuleBasedAssemblyOptimization("Using Y instead of stack",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
(Elidable & HasOpcode(PHA) & DoesntMatterWhatItDoesWith(State.Y)) ~
|
||||||
|
(Not(ConcernsStack) & Not(ConcernsY)).capture(1) ~
|
||||||
|
Where(_.isExternallyLinearBlock(1)) ~
|
||||||
|
(Elidable & HasOpcode(PLA)) ~~> (c =>
|
||||||
|
AssemblyLine.implied(TAY) :: (c.tail.init :+ AssemblyLine.implied(TYA))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: make it more generic
|
||||||
|
val IndexSwitchingOptimization = new RuleBasedAssemblyOptimization("Index switching optimization",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
(Elidable & HasOpcode(LDY) & MatchAddrMode(2) & Not(ReadsX) & MatchParameter(0)) ~
|
||||||
|
(Elidable & Linear & Not(ChangesY) & HasAddrMode(AbsoluteY) & SupportsAbsoluteX & Not(ConcernsX)) ~
|
||||||
|
(HasOpcode(LDY) & Not(ConcernsX)) ~
|
||||||
|
(Linear & Not(ChangesY) & Not(ConcernsX) & HasAddrModeIn(Set(AbsoluteY, IndexedY, ZeroPageY))) ~
|
||||||
|
(Elidable & HasOpcode(LDY) & MatchAddrMode(2) & Not(ReadsX) & MatchParameter(0)) ~
|
||||||
|
(Elidable & Linear & Not(ChangesY) & HasAddrMode(AbsoluteY) & SupportsAbsoluteX & Not(ConcernsX) & DoesntMatterWhatItDoesWith(State.X, State.N, State.Z)) ~~> { (code, ctx) =>
|
||||||
|
List(
|
||||||
|
code(0).copy(opcode = LDX),
|
||||||
|
code(1).copy(addrMode = AbsoluteX),
|
||||||
|
code(2),
|
||||||
|
code(3),
|
||||||
|
code(5).copy(addrMode = AbsoluteX))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDX) & MatchAddrMode(2) & Not(ReadsY) & MatchParameter(0)) ~
|
||||||
|
(Elidable & Linear & Not(ChangesX) & HasAddrMode(AbsoluteX) & SupportsAbsoluteY & Not(ConcernsY)) ~
|
||||||
|
(HasOpcode(LDX) & Not(ConcernsY)) ~
|
||||||
|
(Linear & Not(ChangesX) & Not(ConcernsY) & HasAddrModeIn(Set(AbsoluteX, IndexedX, ZeroPageX, AbsoluteIndexedX))) ~
|
||||||
|
(Elidable & HasOpcode(LDX) & MatchAddrMode(2) & Not(ReadsY) & MatchParameter(0)) ~
|
||||||
|
(Elidable & Linear & Not(ChangesX) & HasAddrMode(AbsoluteX) & SupportsAbsoluteY & Not(ConcernsY) & DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z)) ~~> { (code, ctx) =>
|
||||||
|
List(
|
||||||
|
code(0).copy(opcode = LDY),
|
||||||
|
code(1).copy(addrMode = AbsoluteY),
|
||||||
|
code(2),
|
||||||
|
code(3),
|
||||||
|
code(5).copy(addrMode = AbsoluteY))
|
||||||
|
},
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
val All = List(
|
||||||
|
DoubleLoadToDifferentRegisters,
|
||||||
|
DoubleLoadToTheSameRegister,
|
||||||
|
IndexSwitchingOptimization,
|
||||||
|
PointlessLoadAfterStore,
|
||||||
|
PointessLoadingForShifting,
|
||||||
|
LoadingAfterShifting,
|
||||||
|
UseXInsteadOfStack,
|
||||||
|
UseYInsteadOfStack,
|
||||||
|
UseZeropageAddressingMode)
|
||||||
|
}
|
||||||
|
|
425
src/main/scala/millfork/assembly/opt/QuantumFlowAnalyzer.scala
Normal file
425
src/main/scala/millfork/assembly/opt/QuantumFlowAnalyzer.scala
Normal file
|
@ -0,0 +1,425 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.assembly.{AssemblyLine, OpcodeClasses}
|
||||||
|
import millfork.env.{Label, MemoryAddressConstant, NormalFunction, NumericConstant}
|
||||||
|
|
||||||
|
import scala.collection.immutable.BitSet
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
object QCpuStatus {
|
||||||
|
val InitialStatus = QCpuStatus((for {
|
||||||
|
c <- Seq(true, false)
|
||||||
|
v <- Seq(true, false)
|
||||||
|
n <- Seq(true, false)
|
||||||
|
z <- Seq(true, false)
|
||||||
|
} yield QFlagStatus(c = c, d = false, v = v, n = n, z = z) -> QRegStatus(a = QRegStatus.AllValues, x = QRegStatus.AllValues, y = QRegStatus.AllValues, equal = RegEquality.NoEquality)).toMap)
|
||||||
|
|
||||||
|
val UnknownStatus = QCpuStatus((for {
|
||||||
|
c <- Seq(true, false)
|
||||||
|
v <- Seq(true, false)
|
||||||
|
n <- Seq(true, false)
|
||||||
|
z <- Seq(true, false)
|
||||||
|
} yield QFlagStatus(c = c, d = false, v = v, n = n, z = z) -> QRegStatus(a = QRegStatus.AllValues, x = QRegStatus.AllValues, y = QRegStatus.AllValues, equal = RegEquality.UnknownEquality)).toMap)
|
||||||
|
|
||||||
|
def gather(l: List[(QFlagStatus, QRegStatus)]) =
|
||||||
|
QCpuStatus(l.groupBy(_._1).
|
||||||
|
map { case (k, vs) => k -> vs.map(_._2).reduce(_ ++ _) }.
|
||||||
|
filterNot(_._2.isEmpty))
|
||||||
|
}
|
||||||
|
|
||||||
|
case class QCpuStatus(data: Map[QFlagStatus, QRegStatus]) {
|
||||||
|
def collapse: CpuStatus = {
|
||||||
|
val registers = data.values.reduce(_ ++ _)
|
||||||
|
|
||||||
|
def bitset(b: BitSet): Status[Int] = if (b.size == 1) SingleStatus(b.head) else AnyStatus()
|
||||||
|
|
||||||
|
def flag(f: QFlagStatus => Boolean): Status[Boolean] =
|
||||||
|
if (data.keys.forall(k => f(k))) SingleStatus(true)
|
||||||
|
else if (data.keys.forall(k => !f(k))) SingleStatus(false)
|
||||||
|
else AnyStatus()
|
||||||
|
|
||||||
|
CpuStatus(
|
||||||
|
a = bitset(registers.a),
|
||||||
|
x = bitset(registers.x),
|
||||||
|
y = bitset(registers.y),
|
||||||
|
c = flag(_.c),
|
||||||
|
d = flag(_.d),
|
||||||
|
v = flag(_.v),
|
||||||
|
z = flag(_.z),
|
||||||
|
n = flag(_.n),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def changeFlagUnconditionally(f: QFlagStatus => QFlagStatus): QCpuStatus = {
|
||||||
|
QCpuStatus.gather(data.toList.map { case (k, v) => f(k) -> v })
|
||||||
|
}
|
||||||
|
|
||||||
|
def changeFlagsInAnUnknownWay(f: QFlagStatus => QFlagStatus, g: QFlagStatus => QFlagStatus): QCpuStatus = {
|
||||||
|
QCpuStatus.gather(data.toList.flatMap { case (k, v) => List(f(k) -> v, g(k) -> v) })
|
||||||
|
}
|
||||||
|
|
||||||
|
def changeFlagsInAnUnknownWay(f: QFlagStatus => QFlagStatus, g: QFlagStatus => QFlagStatus, h: QFlagStatus => QFlagStatus): QCpuStatus = {
|
||||||
|
QCpuStatus.gather(data.toList.flatMap { case (k, v) => List(f(k) -> v, g(k) -> v, h(k) -> v) })
|
||||||
|
}
|
||||||
|
|
||||||
|
def mapRegisters(f: QRegStatus => QRegStatus): QCpuStatus = {
|
||||||
|
QCpuStatus(data.map { case (k, v) => k -> f(v) })
|
||||||
|
}
|
||||||
|
|
||||||
|
def mapRegisters(f: (QFlagStatus, QRegStatus) => QRegStatus): QCpuStatus = {
|
||||||
|
QCpuStatus(data.map { case (k, v) => k -> f(k, v) })
|
||||||
|
}
|
||||||
|
|
||||||
|
def flatMap(f: (QFlagStatus, QRegStatus) => List[(QFlagStatus, QRegStatus)]): QCpuStatus = {
|
||||||
|
QCpuStatus.gather(data.toList.flatMap { case (k, v) => f(k, v) })
|
||||||
|
}
|
||||||
|
|
||||||
|
def changeNZFromA: QCpuStatus = {
|
||||||
|
QCpuStatus.gather(data.toList.flatMap { case (k, v) =>
|
||||||
|
List(
|
||||||
|
k.copy(n = false, z = false) -> v.whereA(i => i.toByte > 0),
|
||||||
|
k.copy(n = true, z = false) -> v.whereA(i => i.toByte < 0),
|
||||||
|
k.copy(n = false, z = true) -> v.whereA(i => i.toByte == 0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
def changeNZFromX: QCpuStatus = {
|
||||||
|
QCpuStatus.gather(data.toList.flatMap { case (k, v) =>
|
||||||
|
List(
|
||||||
|
k.copy(n = false, z = false) -> v.whereX(i => i.toByte > 0),
|
||||||
|
k.copy(n = true, z = false) -> v.whereX(i => i.toByte < 0),
|
||||||
|
k.copy(n = false, z = true) -> v.whereX(i => i.toByte == 0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
def changeNZFromY: QCpuStatus = {
|
||||||
|
QCpuStatus.gather(data.toList.flatMap { case (k, v) =>
|
||||||
|
List(
|
||||||
|
k.copy(n = false, z = false) -> v.whereY(i => i.toByte > 0),
|
||||||
|
k.copy(n = true, z = false) -> v.whereY(i => i.toByte < 0),
|
||||||
|
k.copy(n = false, z = true) -> v.whereY(i => i.toByte == 0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
def ~(that: QCpuStatus): QCpuStatus = QCpuStatus.gather(this.data.toList ++ that.data.toList)
|
||||||
|
}
|
||||||
|
|
||||||
|
object QRegStatus {
|
||||||
|
val NoValues: BitSet = BitSet.empty
|
||||||
|
val AllValues: BitSet = BitSet.fromBitMask(Array(-1L, -1L, -1L, -1L))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object RegEquality extends Enumeration {
|
||||||
|
val NoEquality, AX, AY, XY, AXY, UnknownEquality = Value
|
||||||
|
|
||||||
|
def or(a: Value, b: Value) = {
|
||||||
|
(a, b) match {
|
||||||
|
case (UnknownEquality, _) => b
|
||||||
|
case (_, UnknownEquality) => a
|
||||||
|
case (NoEquality, _) => NoEquality
|
||||||
|
case (_, NoEquality) => NoEquality
|
||||||
|
case (_, _) if a == b => a
|
||||||
|
case (AXY, _) => b
|
||||||
|
case (_, AXY) => a
|
||||||
|
case _ => NoEquality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def afterTransfer(a: Value, b: Value) = {
|
||||||
|
(a, b) match {
|
||||||
|
case (UnknownEquality, _) => b
|
||||||
|
case (_, UnknownEquality) => a
|
||||||
|
case (NoEquality, _) => b
|
||||||
|
case (_, NoEquality) => a
|
||||||
|
case (_, _) if a == b => a
|
||||||
|
case _ => AXY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class QRegStatus(a: BitSet, x: BitSet, y: BitSet, equal: RegEquality.Value) {
|
||||||
|
def isEmpty: Boolean = a.isEmpty || x.isEmpty || y.isEmpty
|
||||||
|
|
||||||
|
def ++(that: QRegStatus) = QRegStatus(
|
||||||
|
a = a ++ that.a,
|
||||||
|
x = x ++ that.x,
|
||||||
|
y = y ++ that.y,
|
||||||
|
equal = RegEquality.or(equal, that.equal))
|
||||||
|
|
||||||
|
def afterTransfer(transfer: RegEquality.Value): QRegStatus =
|
||||||
|
copy(equal = RegEquality.afterTransfer(equal, transfer))
|
||||||
|
|
||||||
|
def changeA(f: Int => Long): QRegStatus = {
|
||||||
|
val newA = a.map(i => f(i).toInt & 0xff)
|
||||||
|
val newEqual = equal match {
|
||||||
|
case RegEquality.XY => RegEquality.XY
|
||||||
|
case RegEquality.AXY => RegEquality.XY
|
||||||
|
case _ => RegEquality.NoEquality
|
||||||
|
}
|
||||||
|
QRegStatus(newA, x, y, newEqual)
|
||||||
|
}
|
||||||
|
|
||||||
|
def changeX(f: Int => Long): QRegStatus = {
|
||||||
|
val newA = a.map(i => f(i).toInt & 0xff)
|
||||||
|
val newEqual = equal match {
|
||||||
|
case RegEquality.XY => RegEquality.XY
|
||||||
|
case RegEquality.AXY => RegEquality.XY
|
||||||
|
case _ => RegEquality.NoEquality
|
||||||
|
}
|
||||||
|
QRegStatus(newA, x, y, newEqual)
|
||||||
|
}
|
||||||
|
|
||||||
|
def changeY(f: Int => Long): QRegStatus = {
|
||||||
|
val newA = a.map(i => f(i).toInt & 0xff)
|
||||||
|
val newEqual = equal match {
|
||||||
|
case RegEquality.XY => RegEquality.XY
|
||||||
|
case RegEquality.AXY => RegEquality.XY
|
||||||
|
case _ => RegEquality.NoEquality
|
||||||
|
}
|
||||||
|
QRegStatus(newA, x, y, newEqual)
|
||||||
|
}
|
||||||
|
|
||||||
|
def whereA(f: Int => Boolean): QRegStatus =
|
||||||
|
equal match {
|
||||||
|
case RegEquality.AXY =>
|
||||||
|
copy(a = a.filter(f), x = x.filter(f), y = y.filter(f))
|
||||||
|
case RegEquality.AY =>
|
||||||
|
copy(a = a.filter(f), y = y.filter(f))
|
||||||
|
case RegEquality.AX =>
|
||||||
|
copy(a = a.filter(f), x = x.filter(f))
|
||||||
|
case _ =>
|
||||||
|
copy(a = a.filter(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
def whereX(f: Int => Boolean): QRegStatus =
|
||||||
|
equal match {
|
||||||
|
case RegEquality.AXY =>
|
||||||
|
copy(a = a.filter(f), x = x.filter(f), y = y.filter(f))
|
||||||
|
case RegEquality.XY =>
|
||||||
|
copy(x = x.filter(f), y = y.filter(f))
|
||||||
|
case RegEquality.AX =>
|
||||||
|
copy(a = a.filter(f), x = x.filter(f))
|
||||||
|
case _ =>
|
||||||
|
copy(x = x.filter(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
def whereY(f: Int => Boolean): QRegStatus =
|
||||||
|
equal match {
|
||||||
|
case RegEquality.AXY =>
|
||||||
|
copy(a = a.filter(f), x = x.filter(f), y = y.filter(f))
|
||||||
|
case RegEquality.AY =>
|
||||||
|
copy(a = a.filter(f), y = y.filter(f))
|
||||||
|
case RegEquality.XY =>
|
||||||
|
copy(x = x.filter(f), y = y.filter(f))
|
||||||
|
case _ =>
|
||||||
|
copy(y = y.filter(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class QFlagStatus(c: Boolean, d: Boolean, v: Boolean, z: Boolean, n: Boolean)
|
||||||
|
|
||||||
|
object QuantumFlowAnalyzer {
|
||||||
|
private def loBit(b: Boolean) = if (b) 1 else 0
|
||||||
|
|
||||||
|
private def hiBit(b: Boolean) = if (b) 0x80 else 0
|
||||||
|
|
||||||
|
//noinspection RedundantNewCaseClass
|
||||||
|
def analyze(f: NormalFunction, code: List[AssemblyLine]): List[QCpuStatus] = {
|
||||||
|
val flagArray = Array.fill[QCpuStatus](code.length)(QCpuStatus.UnknownStatus)
|
||||||
|
val codeArray = code.toArray
|
||||||
|
|
||||||
|
var changed = true
|
||||||
|
while (changed) {
|
||||||
|
changed = false
|
||||||
|
var currentStatus: QCpuStatus = if (f.interrupt) QCpuStatus.UnknownStatus else QCpuStatus.UnknownStatus
|
||||||
|
for (i <- codeArray.indices) {
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
if (flagArray(i) != currentStatus) {
|
||||||
|
changed = true
|
||||||
|
flagArray(i) = currentStatus
|
||||||
|
}
|
||||||
|
codeArray(i) match {
|
||||||
|
case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(l)), _) =>
|
||||||
|
val L = l
|
||||||
|
currentStatus = codeArray.indices.flatMap(j => codeArray(j) match {
|
||||||
|
case AssemblyLine(_, _, MemoryAddressConstant(Label(L)), _) => Some(flagArray(j))
|
||||||
|
case _ => None
|
||||||
|
}).fold(QCpuStatus.UnknownStatus)(_ ~ _)
|
||||||
|
|
||||||
|
case AssemblyLine(BCC, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(c = true))
|
||||||
|
case AssemblyLine(BCS, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(c = false))
|
||||||
|
case AssemblyLine(BVS, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(v = false))
|
||||||
|
case AssemblyLine(BVC, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(v = true))
|
||||||
|
case AssemblyLine(BMI, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(n = false))
|
||||||
|
case AssemblyLine(BPL, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(n = true))
|
||||||
|
case AssemblyLine(BEQ, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(z = false))
|
||||||
|
case AssemblyLine(BNE, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(z = true))
|
||||||
|
|
||||||
|
case AssemblyLine(SED, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(d = true))
|
||||||
|
case AssemblyLine(SEC, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(c = true))
|
||||||
|
case AssemblyLine(CLD, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(d = false))
|
||||||
|
case AssemblyLine(CLC, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(c = false))
|
||||||
|
case AssemblyLine(CLV, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.changeFlagUnconditionally(f => f.copy(v = false))
|
||||||
|
|
||||||
|
case AssemblyLine(JSR, _, _, _) =>
|
||||||
|
currentStatus = QCpuStatus.InitialStatus
|
||||||
|
|
||||||
|
case AssemblyLine(LDX, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeX(_ => n)).changeNZFromX
|
||||||
|
case AssemblyLine(LDY, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeY(_ => n)).changeNZFromY
|
||||||
|
case AssemblyLine(LDA, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeA(_ => n)).changeNZFromA
|
||||||
|
case AssemblyLine(LAX, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeA(_ => n).changeX(_ => n).afterTransfer(RegEquality.AX)).changeNZFromA
|
||||||
|
|
||||||
|
case AssemblyLine(EOR, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeA(_ ^ n)).changeNZFromA
|
||||||
|
case AssemblyLine(AND, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeA(_ & n)).changeNZFromA
|
||||||
|
case AssemblyLine(ANC, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeA(_ & n)).changeNZFromA.changeFlagUnconditionally(f => f.copy(c = f.z))
|
||||||
|
case AssemblyLine(ORA, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeA(_ | n)).changeNZFromA
|
||||||
|
|
||||||
|
case AssemblyLine(INX, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeX(_ + 1)).changeNZFromX
|
||||||
|
case AssemblyLine(DEX, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeX(_ - 1)).changeNZFromX
|
||||||
|
case AssemblyLine(INY, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeY(_ - 1)).changeNZFromY
|
||||||
|
case AssemblyLine(DEY, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.changeY(_ - 1)).changeNZFromY
|
||||||
|
case AssemblyLine(TAX, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.copy(x = r.a).afterTransfer(RegEquality.AX)).changeNZFromX
|
||||||
|
case AssemblyLine(TXA, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.copy(a = r.x).afterTransfer(RegEquality.AX)).changeNZFromA
|
||||||
|
case AssemblyLine(TAY, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.copy(y = r.a).afterTransfer(RegEquality.AY)).changeNZFromY
|
||||||
|
case AssemblyLine(TYA, _, _, _) =>
|
||||||
|
currentStatus = currentStatus.mapRegisters(r => r.copy(a = r.y).afterTransfer(RegEquality.AY)).changeNZFromA
|
||||||
|
|
||||||
|
case AssemblyLine(ROL, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.flatMap((f, r) => List(
|
||||||
|
f.copy(c = true) -> r.whereA(a => (a & 0x80) != 0).changeA(a => a * 2 + loBit(f.c)),
|
||||||
|
f.copy(c = false) -> r.whereA(a => (a & 0x80) == 0).changeA(a => a * 2 + loBit(f.c)),
|
||||||
|
)).changeNZFromA
|
||||||
|
case AssemblyLine(ROR, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.flatMap((f, r) => List(
|
||||||
|
f.copy(c = true) -> r.whereA(a => (a & 1) != 0).changeA(a => (a >>> 2) & 0x7f | hiBit(f.c)),
|
||||||
|
f.copy(c = false) -> r.whereA(a => (a & 1) == 0).changeA(a => (a >>> 2) & 0x7f | hiBit(f.c)),
|
||||||
|
)).changeNZFromA
|
||||||
|
case AssemblyLine(ASL, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.flatMap((f, r) => List(
|
||||||
|
f.copy(c = true) -> r.whereA(a => (a & 0x80) != 0).changeA(a => a * 2),
|
||||||
|
f.copy(c = false) -> r.whereA(a => (a & 0x80) == 0).changeA(a => a * 2),
|
||||||
|
)).changeNZFromA
|
||||||
|
case AssemblyLine(LSR, Implied, _, _) =>
|
||||||
|
currentStatus = currentStatus.flatMap((f, r) => List(
|
||||||
|
f.copy(c = true) -> r.whereA(a => (a & 1) != 0).changeA(a => (a >>> 2) & 0x7f),
|
||||||
|
f.copy(c = false) -> r.whereA(a => (a & 1) == 0).changeA(a => (a >>> 2) & 0x7f),
|
||||||
|
)).changeNZFromA
|
||||||
|
case AssemblyLine(ALR, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.flatMap((f, r) => List(
|
||||||
|
f.copy(c = true) -> r.whereA(a => (a & n & 1) != 0).changeA(a => ((a & n) >>> 2) & 0x7f),
|
||||||
|
f.copy(c = false) -> r.whereA(a => (a & n & 1) == 0).changeA(a => ((a & n) >>> 2) & 0x7f),
|
||||||
|
)).changeNZFromA
|
||||||
|
case AssemblyLine(ADC, Immediate, NumericConstant(nn, _), _) =>
|
||||||
|
val n = nn & 0xff
|
||||||
|
currentStatus = currentStatus.flatMap((f, r) =>
|
||||||
|
if (f.d) {
|
||||||
|
val regs = r.copy(a = QRegStatus.AllValues).changeA(_.toLong)
|
||||||
|
List(
|
||||||
|
f.copy(c = false, v = false) -> regs,
|
||||||
|
f.copy(c = true, v = false) -> regs,
|
||||||
|
f.copy(c = false, v = true) -> regs,
|
||||||
|
f.copy(c = true, v = true) -> regs,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if (f.c) {
|
||||||
|
val regs = r.changeA(_ + n + 1)
|
||||||
|
List(
|
||||||
|
f.copy(c = false, v = false) -> regs.whereA(_ >= n),
|
||||||
|
f.copy(c = true, v = false) -> regs.whereA(_ < n),
|
||||||
|
f.copy(c = false, v = true) -> regs.whereA(_ >= n),
|
||||||
|
f.copy(c = true, v = true) -> regs.whereA(_ < n),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val regs = r.changeA(_ + n)
|
||||||
|
List(
|
||||||
|
f.copy(c = false, v = false) -> regs.whereA(_ > n),
|
||||||
|
f.copy(c = true, v = false) -> regs.whereA(_ <= n),
|
||||||
|
f.copy(c = false, v = true) -> regs.whereA(_ > n),
|
||||||
|
f.copy(c = true, v = true) -> regs.whereA(_ <= n),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).changeNZFromA
|
||||||
|
case AssemblyLine(SBC, Immediate, NumericConstant(n, _), _) =>
|
||||||
|
currentStatus = currentStatus.flatMap((f, r) =>
|
||||||
|
if (f.d) {
|
||||||
|
val regs = r.copy(a = QRegStatus.AllValues).changeA(_.toLong)
|
||||||
|
// TODO: guess the carry flag correctly
|
||||||
|
List(
|
||||||
|
f.copy(c = false, v = false) -> regs,
|
||||||
|
f.copy(c = true, v = false) -> regs,
|
||||||
|
f.copy(c = false, v = true) -> regs,
|
||||||
|
f.copy(c = true, v = true) -> regs,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val regs = if (f.c) r.changeA(_ - n) else r.changeA(_ - n - 1)
|
||||||
|
List(
|
||||||
|
f.copy(c = false, v = false) -> regs,
|
||||||
|
f.copy(c = true, v = false) -> regs,
|
||||||
|
f.copy(c = false, v = true) -> regs,
|
||||||
|
f.copy(c = true, v = true) -> regs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).changeNZFromA
|
||||||
|
|
||||||
|
case AssemblyLine(opcode, addrMode, parameter, _) =>
|
||||||
|
if (OpcodeClasses.ChangesX(opcode)) currentStatus = currentStatus.mapRegisters(r => r.copy(x = QRegStatus.AllValues))
|
||||||
|
if (OpcodeClasses.ChangesY(opcode)) currentStatus = currentStatus.mapRegisters(r => r.copy(y = QRegStatus.AllValues))
|
||||||
|
if (OpcodeClasses.ChangesAAlways(opcode)) currentStatus = currentStatus.mapRegisters(r => r.copy(a = QRegStatus.AllValues))
|
||||||
|
if (addrMode == Implied && OpcodeClasses.ChangesAIfImplied(opcode)) currentStatus = currentStatus.mapRegisters(r => r.copy(a = QRegStatus.AllValues))
|
||||||
|
if (OpcodeClasses.ChangesNAndZ(opcode)) currentStatus = currentStatus.changeFlagsInAnUnknownWay(
|
||||||
|
_.copy(n = false, z = false),
|
||||||
|
_.copy(n = true, z = false),
|
||||||
|
_.copy(n = false, z = true))
|
||||||
|
if (OpcodeClasses.ChangesC(opcode)) currentStatus = currentStatus.changeFlagsInAnUnknownWay(_.copy(c = false), _.copy(c = true))
|
||||||
|
if (OpcodeClasses.ChangesV(opcode)) currentStatus = currentStatus.changeFlagsInAnUnknownWay(_.copy(v = false), _.copy(v = true))
|
||||||
|
if (opcode == CMP || opcode == CPX || opcode == CPY) {
|
||||||
|
if (addrMode == Immediate) parameter match {
|
||||||
|
case NumericConstant(0, _) => currentStatus = currentStatus.changeFlagUnconditionally(_.copy(c = true))
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// flagArray.zip(codeArray).foreach{
|
||||||
|
// case (fl, y) => if (y.isPrintable) println(f"$fl%-32s $y%-32s")
|
||||||
|
// }
|
||||||
|
// println("---------------------")
|
||||||
|
}
|
||||||
|
|
||||||
|
flagArray.toList
|
||||||
|
}
|
||||||
|
}
|
149
src/main/scala/millfork/assembly/opt/ReverseFlowAnalyzer.scala
Normal file
149
src/main/scala/millfork/assembly/opt/ReverseFlowAnalyzer.scala
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.assembly.{AssemblyLine, OpcodeClasses, State}
|
||||||
|
import millfork.env.{Label, MemoryAddressConstant, NormalFunction, NumericConstant}
|
||||||
|
|
||||||
|
import scala.collection.immutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
sealed trait Importance {
|
||||||
|
def ~(that: Importance) = (this, that) match {
|
||||||
|
case (_, Important) | (Important, _) => Important
|
||||||
|
case (_, Unimportant) | (Unimportant, _) => Unimportant
|
||||||
|
case (UnknownImportance, UnknownImportance) => UnknownImportance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case object Important extends Importance {
|
||||||
|
override def toString = "!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case object Unimportant extends Importance {
|
||||||
|
override def toString = "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
case object UnknownImportance extends Importance {
|
||||||
|
override def toString = "?"
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection RedundantNewCaseClass
|
||||||
|
case class CpuImportance(a: Importance = UnknownImportance,
|
||||||
|
x: Importance = UnknownImportance,
|
||||||
|
y: Importance = UnknownImportance,
|
||||||
|
n: Importance = UnknownImportance,
|
||||||
|
z: Importance = UnknownImportance,
|
||||||
|
v: Importance = UnknownImportance,
|
||||||
|
c: Importance = UnknownImportance,
|
||||||
|
d: Importance = UnknownImportance,
|
||||||
|
) {
|
||||||
|
override def toString: String = s"A=$a,X=$x,Y=$y,Z=$z,N=$n,C=$c,V=$v,D=$d"
|
||||||
|
|
||||||
|
def ~(that: CpuImportance) = new CpuImportance(
|
||||||
|
a = this.a ~ that.a,
|
||||||
|
x = this.x ~ that.x,
|
||||||
|
y = this.y ~ that.y,
|
||||||
|
z = this.z ~ that.z,
|
||||||
|
n = this.n ~ that.n,
|
||||||
|
c = this.c ~ that.c,
|
||||||
|
v = this.v ~ that.v,
|
||||||
|
d = this.d ~ that.d,
|
||||||
|
)
|
||||||
|
|
||||||
|
def isUnimportant(state: State.Value): Boolean = state match {
|
||||||
|
case State.A => a == Unimportant
|
||||||
|
case State.X => x == Unimportant
|
||||||
|
case State.Y => y == Unimportant
|
||||||
|
case State.Z => z == Unimportant
|
||||||
|
case State.N => n == Unimportant
|
||||||
|
case State.C => c == Unimportant
|
||||||
|
case State.V => v == Unimportant
|
||||||
|
case State.D => d == Unimportant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ReverseFlowAnalyzer {
|
||||||
|
//noinspection RedundantNewCaseClass
|
||||||
|
def analyze(f: NormalFunction, code: List[AssemblyLine]): List[CpuImportance] = {
|
||||||
|
val importanceArray = Array.fill[CpuImportance](code.length)(new CpuImportance())
|
||||||
|
val codeArray = code.toArray
|
||||||
|
val initialStatus = new CpuStatus(d = SingleStatus(false))
|
||||||
|
|
||||||
|
var changed = true
|
||||||
|
val finalImportance = new CpuImportance(a = Important, x = Important, y = Important, c = Important, v = Important, d = Important, z = Important, n = Important)
|
||||||
|
changed = true
|
||||||
|
while (changed) {
|
||||||
|
changed = false
|
||||||
|
var currentImportance: CpuImportance = finalImportance
|
||||||
|
for (i <- codeArray.indices.reverse) {
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
if (importanceArray(i) != currentImportance) {
|
||||||
|
changed = true
|
||||||
|
importanceArray(i) = currentImportance
|
||||||
|
}
|
||||||
|
codeArray(i) match {
|
||||||
|
case AssemblyLine(opcode, Relative, MemoryAddressConstant(Label(l)), _) if OpcodeClasses.ShortBranching(opcode) =>
|
||||||
|
val L = l
|
||||||
|
val labelIndex = codeArray.indexWhere {
|
||||||
|
case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(L)), _) => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
currentImportance = if (labelIndex < 0) finalImportance else importanceArray(labelIndex) ~ currentImportance
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
codeArray(i) match {
|
||||||
|
// TODO: JSR?
|
||||||
|
case AssemblyLine(JMP, Absolute, MemoryAddressConstant(Label(l)), _) =>
|
||||||
|
val L = l
|
||||||
|
val labelIndex = codeArray.indexWhere {
|
||||||
|
case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(L)), _) => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
currentImportance = if (labelIndex < 0) finalImportance else importanceArray(labelIndex)
|
||||||
|
case AssemblyLine(JMP, Indirect, _, _) =>
|
||||||
|
currentImportance = finalImportance
|
||||||
|
case AssemblyLine(BNE | BEQ, _, _, _) =>
|
||||||
|
currentImportance = currentImportance.copy(z = Important)
|
||||||
|
case AssemblyLine(BMI | BPL, _, _, _) =>
|
||||||
|
currentImportance = currentImportance.copy(n = Important)
|
||||||
|
case AssemblyLine(SED | CLD, _, _, _) =>
|
||||||
|
currentImportance = currentImportance.copy(d = Unimportant)
|
||||||
|
case AssemblyLine(RTS, _, _, _) =>
|
||||||
|
currentImportance = finalImportance
|
||||||
|
case AssemblyLine(DISCARD_XF, _, _, _) =>
|
||||||
|
currentImportance = currentImportance.copy(x = Unimportant, n = Unimportant, z = Unimportant, c = Unimportant, v = Unimportant)
|
||||||
|
case AssemblyLine(DISCARD_YF, _, _, _) =>
|
||||||
|
currentImportance = currentImportance.copy(y = Unimportant, n = Unimportant, z = Unimportant, c = Unimportant, v = Unimportant)
|
||||||
|
case AssemblyLine(DISCARD_AF, _, _, _) =>
|
||||||
|
currentImportance = currentImportance.copy(a = Unimportant, n = Unimportant, z = Unimportant, c = Unimportant, v = Unimportant)
|
||||||
|
case AssemblyLine(opcode, addrMode, _, _) =>
|
||||||
|
if (OpcodeClasses.ChangesC(opcode)) currentImportance = currentImportance.copy(c = Unimportant)
|
||||||
|
if (OpcodeClasses.ChangesV(opcode)) currentImportance = currentImportance.copy(v = Unimportant)
|
||||||
|
if (OpcodeClasses.ChangesNAndZ(opcode)) currentImportance = currentImportance.copy(n = Unimportant, z = Unimportant)
|
||||||
|
if (OpcodeClasses.OverwritesA(opcode)) currentImportance = currentImportance.copy(a = Unimportant)
|
||||||
|
if (OpcodeClasses.OverwritesX(opcode)) currentImportance = currentImportance.copy(x = Unimportant)
|
||||||
|
if (OpcodeClasses.OverwritesY(opcode)) currentImportance = currentImportance.copy(y = Unimportant)
|
||||||
|
if (OpcodeClasses.ReadsC(opcode)) currentImportance = currentImportance.copy(c = Important)
|
||||||
|
if (OpcodeClasses.ReadsD(opcode)) currentImportance = currentImportance.copy(d = Important)
|
||||||
|
if (OpcodeClasses.ReadsV(opcode)) currentImportance = currentImportance.copy(v = Important)
|
||||||
|
if (OpcodeClasses.ReadsXAlways(opcode)) currentImportance = currentImportance.copy(x = Important)
|
||||||
|
if (OpcodeClasses.ReadsYAlways(opcode)) currentImportance = currentImportance.copy(y = Important)
|
||||||
|
if (OpcodeClasses.ReadsAAlways(opcode)) currentImportance = currentImportance.copy(a = Important)
|
||||||
|
if (OpcodeClasses.ReadsAIfImplied(opcode) && addrMode == Implied) currentImportance = currentImportance.copy(a = Important)
|
||||||
|
if (addrMode == AbsoluteX || addrMode == IndexedX || addrMode == ZeroPageX) currentImportance = currentImportance.copy(x = Important)
|
||||||
|
if (addrMode == AbsoluteY || addrMode == IndexedY || addrMode == ZeroPageY) currentImportance = currentImportance.copy(y = Important)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// importanceArray.zip(codeArray).foreach{
|
||||||
|
// case (i, y) => if (y.isPrintable) println(f"$y%-32s $i%-32s")
|
||||||
|
// }
|
||||||
|
// println("---------------------")
|
||||||
|
|
||||||
|
importanceArray.toList
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,757 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.{CompilationFlag, CompilationOptions}
|
||||||
|
import millfork.assembly._
|
||||||
|
import millfork.env._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
object FlowInfoRequirement extends Enumeration {
|
||||||
|
|
||||||
|
val NoRequirement, BothFlows, ForwardFlow, BackwardFlow = Value
|
||||||
|
|
||||||
|
def assertForward(x: FlowInfoRequirement.Value): Unit = x match {
|
||||||
|
case BothFlows | ForwardFlow => ()
|
||||||
|
case NoRequirement | BackwardFlow => ErrorReporting.fatal("Forward flow info required")
|
||||||
|
}
|
||||||
|
|
||||||
|
def assertBackward(x: FlowInfoRequirement.Value): Unit = x match {
|
||||||
|
case BothFlows | BackwardFlow => ()
|
||||||
|
case NoRequirement | ForwardFlow => ErrorReporting.fatal("Backward flow info required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInfoRequirement.Value, val rules: AssemblyRule*) extends AssemblyOptimization {
|
||||||
|
|
||||||
|
rules.foreach(_.pattern.validate(needsFlowInfo))
|
||||||
|
|
||||||
|
override def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = {
|
||||||
|
val effectiveCode = code.map(a => a.copy(parameter = a.parameter.quickSimplify))
|
||||||
|
val taggedCode = needsFlowInfo match {
|
||||||
|
case FlowInfoRequirement.NoRequirement => effectiveCode.map(FlowInfo.Default -> _)
|
||||||
|
case FlowInfoRequirement.BothFlows => FlowAnalyzer.analyze(f, effectiveCode, options)
|
||||||
|
case FlowInfoRequirement.ForwardFlow =>
|
||||||
|
if (options.flag(CompilationFlag.DetailedFlowAnalysis)) {
|
||||||
|
QuantumFlowAnalyzer.analyze(f, code).map(s => FlowInfo(s.collapse, CpuImportance())).zip(code)
|
||||||
|
} else {
|
||||||
|
CoarseFlowAnalyzer.analyze(f, code).map(s => FlowInfo(s, CpuImportance())).zip(code)
|
||||||
|
}
|
||||||
|
case FlowInfoRequirement.BackwardFlow =>
|
||||||
|
ReverseFlowAnalyzer.analyze(f, code).map(i => FlowInfo(CpuStatus(), i)).zip(code)
|
||||||
|
}
|
||||||
|
optimizeImpl(f, taggedCode, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, AssemblyLine)], options: CompilationOptions): List[AssemblyLine] = {
|
||||||
|
code match {
|
||||||
|
case Nil => Nil
|
||||||
|
case head :: tail =>
|
||||||
|
for ((rule, index) <- rules.zipWithIndex) {
|
||||||
|
val ctx = new AssemblyMatchingContext
|
||||||
|
rule.pattern.matchTo(ctx, code) match {
|
||||||
|
case Some(rest: List[(FlowInfo, AssemblyLine)]) =>
|
||||||
|
val matchedChunkToOptimize: List[AssemblyLine] = code.take(code.length - rest.length).map(_._2)
|
||||||
|
val optimizedChunk: List[AssemblyLine] = rule.result(matchedChunkToOptimize, ctx)
|
||||||
|
ErrorReporting.debug(s"Applied $name ($index)")
|
||||||
|
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
|
||||||
|
val before = code.head._1.statusBefore
|
||||||
|
val after = code(matchedChunkToOptimize.length - 1)._1.importanceAfter
|
||||||
|
ErrorReporting.trace(s"Before: $before")
|
||||||
|
ErrorReporting.trace(s"After: $after")
|
||||||
|
}
|
||||||
|
matchedChunkToOptimize.filter(_.isPrintable).foreach(l => ErrorReporting.trace(l.toString))
|
||||||
|
ErrorReporting.trace(" ↓")
|
||||||
|
optimizedChunk.filter(_.isPrintable).foreach(l => ErrorReporting.trace(l.toString))
|
||||||
|
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
|
||||||
|
return optimizedChunk ++ optimizeImpl(f, rest, options)
|
||||||
|
} else {
|
||||||
|
return optimize(f, optimizedChunk ++ rest.map(_._2), options)
|
||||||
|
}
|
||||||
|
case None => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
head._2 :: optimizeImpl(f, tail, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssemblyMatchingContext {
|
||||||
|
private val map = mutable.Map[Int, Any]()
|
||||||
|
|
||||||
|
def addObject(i: Int, o: Any): Boolean = {
|
||||||
|
if (map.contains(i)) {
|
||||||
|
map(i) == o
|
||||||
|
} else {
|
||||||
|
map(i) = o
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def dontMatch(i: Int, o: Any): Boolean = {
|
||||||
|
if (map.contains(i)) {
|
||||||
|
map(i) != o
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def get[T: Manifest](i: Int): T = {
|
||||||
|
val t = map(i)
|
||||||
|
val clazz = implicitly[Manifest[T]].runtimeClass match {
|
||||||
|
case java.lang.Integer.TYPE => classOf[java.lang.Integer]
|
||||||
|
case java.lang.Boolean.TYPE => classOf[java.lang.Boolean]
|
||||||
|
// TODO
|
||||||
|
case x => x
|
||||||
|
}
|
||||||
|
if (clazz.isInstance(t)) {
|
||||||
|
t.asInstanceOf[T]
|
||||||
|
} else {
|
||||||
|
if (i eq null) {
|
||||||
|
ErrorReporting.fatal(s"Value at index $i is null")
|
||||||
|
} else {
|
||||||
|
ErrorReporting.fatal(s"Value at index $i is a ${t.getClass.getSimpleName}, not a ${clazz.getSimpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def isExternallyLinearBlock(i: Int): Boolean = {
|
||||||
|
val labels = mutable.Set[String]()
|
||||||
|
val jumps = mutable.Set[String]()
|
||||||
|
get[List[AssemblyLine]](i).foreach {
|
||||||
|
case AssemblyLine(Opcode.RTS | Opcode.RTI | Opcode.BRK, _, _, _) =>
|
||||||
|
return false
|
||||||
|
case AssemblyLine(Opcode.JMP, AddrMode.Indirect, _, _) =>
|
||||||
|
return false
|
||||||
|
case AssemblyLine(Opcode.LABEL, _, MemoryAddressConstant(Label(l)), _) =>
|
||||||
|
labels += l
|
||||||
|
case AssemblyLine(Opcode.JMP, AddrMode.Absolute, MemoryAddressConstant(Label(l)), _) =>
|
||||||
|
jumps += l
|
||||||
|
case AssemblyLine(Opcode.JMP, AddrMode.Absolute, _, _) =>
|
||||||
|
return false
|
||||||
|
case AssemblyLine(_, AddrMode.Relative, MemoryAddressConstant(Label(l)), _) =>
|
||||||
|
jumps += l
|
||||||
|
case AssemblyLine(br, _, _, _) if OpcodeClasses.ShortBranching(br) =>
|
||||||
|
return false
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
// if a jump leads inside the block, then it's internal
|
||||||
|
// if a jump leads outside the block, then it's external
|
||||||
|
jumps --= labels
|
||||||
|
jumps.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
def areMemoryReferencesProvablyNonOverlapping(param1: Int, addrMode1: Int, param2: Int, addrMode2: Int): Boolean = {
|
||||||
|
val p1 = get[Constant](param1).quickSimplify
|
||||||
|
val a1 = get[AddrMode.Value](addrMode1)
|
||||||
|
val p2 = get[Constant](param2).quickSimplify
|
||||||
|
val a2 = get[AddrMode.Value](addrMode2)
|
||||||
|
import AddrMode._
|
||||||
|
val badAddrModes = Set(IndexedX, IndexedY, ZeroPageIndirect, AbsoluteIndexedX)
|
||||||
|
if (badAddrModes(a1) || badAddrModes(a2)) return false
|
||||||
|
|
||||||
|
def handleKnownDistance(distance: Short): Boolean = {
|
||||||
|
val indexingAddrModes = Set(AbsoluteIndexedX, AbsoluteX, ZeroPageX, AbsoluteY, ZeroPageY)
|
||||||
|
val a1Indexing = indexingAddrModes(a1)
|
||||||
|
val a2Indexing = indexingAddrModes(a2)
|
||||||
|
(a1Indexing, a2Indexing) match {
|
||||||
|
case (false, false) => distance != 0
|
||||||
|
case (true, false) => distance > 255 || distance < 0
|
||||||
|
case (false, true) => distance > 0 || distance < -255
|
||||||
|
case (true, true) => distance > 255 || distance < -255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(p1, p2) match {
|
||||||
|
case (NumericConstant(n1, _), NumericConstant(n2, _)) =>
|
||||||
|
handleKnownDistance((n2 - n1).toShort)
|
||||||
|
case (a, CompoundConstant(MathOperator.Plus, b, NumericConstant(distance, _))) if a.quickSimplify == b.quickSimplify =>
|
||||||
|
handleKnownDistance(distance.toShort)
|
||||||
|
case (CompoundConstant(MathOperator.Plus, a, NumericConstant(distance, _)), b) if a.quickSimplify == b.quickSimplify =>
|
||||||
|
handleKnownDistance((-distance).toShort)
|
||||||
|
case (a, CompoundConstant(MathOperator.Minus, b, NumericConstant(distance, _))) if a.quickSimplify == b.quickSimplify =>
|
||||||
|
handleKnownDistance((-distance).toShort)
|
||||||
|
case (CompoundConstant(MathOperator.Minus, a, NumericConstant(distance, _)), b) if a.quickSimplify == b.quickSimplify =>
|
||||||
|
handleKnownDistance(distance.toShort)
|
||||||
|
case (MemoryAddressConstant(MemoryVariable(a, _, _)), MemoryAddressConstant(MemoryVariable(b, _, _))) =>
|
||||||
|
a.takeWhile(_ != '.') != a.takeWhile(_ != '.') // TODO: ???
|
||||||
|
case _ =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class AssemblyRule(pattern: AssemblyPattern, result: (List[AssemblyLine], AssemblyMatchingContext) => List[AssemblyLine]) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
trait AssemblyPattern {
|
||||||
|
|
||||||
|
def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = ()
|
||||||
|
|
||||||
|
def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]]
|
||||||
|
|
||||||
|
def ~(x: AssemblyPattern) = Concatenation(this, x)
|
||||||
|
|
||||||
|
def ~(x: AssemblyLinePattern) = Concatenation(this, x)
|
||||||
|
|
||||||
|
def ~~>(result: (List[AssemblyLine], AssemblyMatchingContext) => List[AssemblyLine]) = AssemblyRule(this, result)
|
||||||
|
|
||||||
|
def ~~>(result: List[AssemblyLine] => List[AssemblyLine]) = AssemblyRule(this, (code, _) => result(code))
|
||||||
|
|
||||||
|
def capture(i: Int) = Capture(i, this)
|
||||||
|
|
||||||
|
def captureLength(i: Int) = CaptureLength(i, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
|
||||||
|
override def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] =
|
||||||
|
for {
|
||||||
|
rest <- pattern.matchTo(ctx, code)
|
||||||
|
} yield {
|
||||||
|
ctx.addObject(i, code.take(code.length - rest.length).map(_._2))
|
||||||
|
rest
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"(?<$i>$pattern)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
|
||||||
|
override def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] =
|
||||||
|
for {
|
||||||
|
rest <- pattern.matchTo(ctx, code)
|
||||||
|
} yield {
|
||||||
|
ctx.addObject(i, code.length - rest.length)
|
||||||
|
rest
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"(?<$i>$pattern)"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case class Where(predicate: (AssemblyMatchingContext => Boolean)) extends AssemblyPattern {
|
||||||
|
def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = {
|
||||||
|
if (predicate(ctx)) Some(code) else None
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = "Where(...)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends AssemblyPattern {
|
||||||
|
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = {
|
||||||
|
l.validate(needsFlowInfo)
|
||||||
|
r.validate(needsFlowInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = {
|
||||||
|
for {
|
||||||
|
middle <- l.matchTo(ctx, code)
|
||||||
|
end <- r.matchTo(ctx, middle)
|
||||||
|
} yield end
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = (l, r) match {
|
||||||
|
case (_: Both, _: Both) => s"($l) · ($r)"
|
||||||
|
case (_, _: Both) => s"$l · ($r)"
|
||||||
|
case (_: Both, _) => s"($l) · $r"
|
||||||
|
case _ => s"$l · $r"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Many(rule: AssemblyLinePattern) extends AssemblyPattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = {
|
||||||
|
rule.validate(needsFlowInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = {
|
||||||
|
var c = code
|
||||||
|
while (true) {
|
||||||
|
c match {
|
||||||
|
case Nil =>
|
||||||
|
return Some(Nil)
|
||||||
|
case x :: xs =>
|
||||||
|
if (rule.matchLineTo(ctx, x._1, x._2)) {
|
||||||
|
c = xs
|
||||||
|
} else {
|
||||||
|
return Some(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"[$rule]*"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ManyWhereAtLeastOne(rule: AssemblyLinePattern, atLeastOneIsThis: AssemblyLinePattern) extends AssemblyPattern {
|
||||||
|
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = {
|
||||||
|
rule.validate(needsFlowInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = {
|
||||||
|
var c = code
|
||||||
|
var oneFound = false
|
||||||
|
while (true) {
|
||||||
|
c match {
|
||||||
|
case Nil =>
|
||||||
|
return Some(Nil)
|
||||||
|
case x :: xs =>
|
||||||
|
if (atLeastOneIsThis.matchLineTo(ctx, x._1, x._2)) {
|
||||||
|
oneFound = true
|
||||||
|
}
|
||||||
|
if (rule.matchLineTo(ctx, x._1, x._2)) {
|
||||||
|
c = xs
|
||||||
|
} else {
|
||||||
|
if (oneFound) {
|
||||||
|
return Some(c)
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"[∃$atLeastOneIsThis:$rule]*"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Opt(rule: AssemblyLinePattern) extends AssemblyPattern {
|
||||||
|
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = {
|
||||||
|
rule.validate(needsFlowInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = {
|
||||||
|
code match {
|
||||||
|
case Nil =>
|
||||||
|
Some(Nil)
|
||||||
|
case x :: xs =>
|
||||||
|
if (rule.matchLineTo(ctx, x._1, x._2)) {
|
||||||
|
Some(xs)
|
||||||
|
} else {
|
||||||
|
Some(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"[$rule]?"
|
||||||
|
}
|
||||||
|
|
||||||
|
trait AssemblyLinePattern extends AssemblyPattern {
|
||||||
|
def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = code match {
|
||||||
|
case Nil => None
|
||||||
|
case x :: xs => if (matchLineTo(ctx, x._1, x._2)) Some(xs) else None
|
||||||
|
}
|
||||||
|
|
||||||
|
def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean
|
||||||
|
|
||||||
|
def unary_! : AssemblyLinePattern = Not(this)
|
||||||
|
|
||||||
|
def ? : AssemblyPattern = Opt(this)
|
||||||
|
|
||||||
|
def * : AssemblyPattern = Many(this)
|
||||||
|
|
||||||
|
def + : AssemblyPattern = this ~ Many(this)
|
||||||
|
|
||||||
|
def |(x: AssemblyLinePattern): AssemblyLinePattern = Either(this, x)
|
||||||
|
|
||||||
|
def &(x: AssemblyLinePattern): AssemblyLinePattern = Both(this, x)
|
||||||
|
|
||||||
|
protected def memoryAccessDoesntOverlap(a1: AddrMode.Value, p1: Constant, a2: AddrMode.Value, p2: Constant): Boolean = {
|
||||||
|
import AddrMode._
|
||||||
|
val badAddrModes = Set(IndexedX, IndexedY, ZeroPageIndirect, AbsoluteIndexedX)
|
||||||
|
if (badAddrModes(a1) || badAddrModes(a2)) return false
|
||||||
|
val goodAddrModes = Set(Implied, Immediate, Relative)
|
||||||
|
if (goodAddrModes(a1) || goodAddrModes(a2)) return true
|
||||||
|
|
||||||
|
def handleKnownDistance(distance: Short): Boolean = {
|
||||||
|
val indexingAddrModes = Set(AbsoluteIndexedX, AbsoluteX, ZeroPageX, AbsoluteY, ZeroPageY)
|
||||||
|
val a1Indexing = indexingAddrModes(a1)
|
||||||
|
val a2Indexing = indexingAddrModes(a2)
|
||||||
|
(a1Indexing, a2Indexing) match {
|
||||||
|
case (false, false) => distance != 0
|
||||||
|
case (true, false) => distance > 255 || distance < 0
|
||||||
|
case (false, true) => distance > 0 || distance < -255
|
||||||
|
case (true, true) => distance > 255 || distance < -255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(p1.quickSimplify, p2.quickSimplify) match {
|
||||||
|
case (NumericConstant(n1, _), NumericConstant(n2, _)) =>
|
||||||
|
handleKnownDistance((n2 - n1).toShort)
|
||||||
|
case (a, CompoundConstant(MathOperator.Plus, b, NumericConstant(distance, _))) if a.quickSimplify == b.quickSimplify =>
|
||||||
|
handleKnownDistance(distance.toShort)
|
||||||
|
case (CompoundConstant(MathOperator.Plus, a, NumericConstant(distance, _)), b) if a.quickSimplify == b.quickSimplify =>
|
||||||
|
handleKnownDistance((-distance).toShort)
|
||||||
|
case (a, CompoundConstant(MathOperator.Minus, b, NumericConstant(distance, _))) if a.quickSimplify == b.quickSimplify =>
|
||||||
|
handleKnownDistance((-distance).toShort)
|
||||||
|
case (CompoundConstant(MathOperator.Minus, a, NumericConstant(distance, _)), b) if a.quickSimplify == b.quickSimplify =>
|
||||||
|
handleKnownDistance(distance.toShort)
|
||||||
|
case (MemoryAddressConstant(a: ThingInMemory), MemoryAddressConstant(b:ThingInMemory)) =>
|
||||||
|
a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ???
|
||||||
|
case (CompoundConstant(MathOperator.Plus | MathOperator.Minus, MemoryAddressConstant(a: ThingInMemory), NumericConstant(_, _)),
|
||||||
|
MemoryAddressConstant(b: ThingInMemory)) =>
|
||||||
|
a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ???
|
||||||
|
case (MemoryAddressConstant(a: ThingInMemory),
|
||||||
|
CompoundConstant(MathOperator.Plus | MathOperator.Minus, MemoryAddressConstant(b: ThingInMemory), NumericConstant(_, _))) =>
|
||||||
|
a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ???
|
||||||
|
case (CompoundConstant(MathOperator.Plus | MathOperator.Minus, MemoryAddressConstant(a: ThingInMemory), NumericConstant(_, _)),
|
||||||
|
CompoundConstant(MathOperator.Plus | MathOperator.Minus, MemoryAddressConstant(b: ThingInMemory), NumericConstant(_, _))) =>
|
||||||
|
a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ???
|
||||||
|
case _ =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection LanguageFeature
|
||||||
|
object AssemblyLinePattern {
|
||||||
|
implicit def __implicitOpcodeIn(ops: Set[Opcode.Value]): AssemblyLinePattern = HasOpcodeIn(ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MatchA(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
|
||||||
|
FlowInfoRequirement.assertForward(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
flowInfo.statusBefore.a match {
|
||||||
|
case SingleStatus(value) => ctx.addObject(i, value)
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MatchX(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
|
||||||
|
FlowInfoRequirement.assertForward(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
flowInfo.statusBefore.x match {
|
||||||
|
case SingleStatus(value) => ctx.addObject(i, value)
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MatchY(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
|
||||||
|
FlowInfoRequirement.assertForward(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
flowInfo.statusBefore.y match {
|
||||||
|
case SingleStatus(value) => ctx.addObject(i, value)
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasA(value: Int) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
|
||||||
|
FlowInfoRequirement.assertForward(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
flowInfo.statusBefore.a.contains(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasX(value: Int) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
|
||||||
|
FlowInfoRequirement.assertForward(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
flowInfo.statusBefore.x.contains(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasY(value: Int) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
|
||||||
|
FlowInfoRequirement.assertForward(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
flowInfo.statusBefore.y.contains(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DoesntMatterWhatItDoesWith(states: State.Value*) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
|
||||||
|
FlowInfoRequirement.assertBackward(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
states.forall(state => flowInfo.importanceAfter.isUnimportant(state))
|
||||||
|
|
||||||
|
override def toString: String = states.mkString("[¯\\_(ツ)_/¯:", ",", "]")
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasSet(state: State.Value) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
|
||||||
|
FlowInfoRequirement.assertForward(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
flowInfo.hasSet(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasClear(state: State.Value) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
|
||||||
|
FlowInfoRequirement.assertForward(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
flowInfo.hasClear(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object Anything extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Not(inner: AssemblyLinePattern) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = inner.validate(needsFlowInfo)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
!inner.matchLineTo(ctx, flowInfo, line)
|
||||||
|
|
||||||
|
override def toString: String = "¬" + inner
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Both(l: AssemblyLinePattern, r: AssemblyLinePattern) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = {
|
||||||
|
l.validate(needsFlowInfo)
|
||||||
|
r.validate(needsFlowInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
l.matchLineTo(ctx, flowInfo, line) && r.matchLineTo(ctx, flowInfo, line)
|
||||||
|
|
||||||
|
override def toString: String = l + " ∧ " + r
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Either(l: AssemblyLinePattern, r: AssemblyLinePattern) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = {
|
||||||
|
l.validate(needsFlowInfo)
|
||||||
|
r.validate(needsFlowInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
l.matchLineTo(ctx, flowInfo, line) || r.matchLineTo(ctx, flowInfo, line)
|
||||||
|
|
||||||
|
override def toString: String = s"($l ∨ $r)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case object Elidable extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
line.elidable
|
||||||
|
}
|
||||||
|
|
||||||
|
case object Linear extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.AllLinear(line.opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object LinearOrBranch extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.AllLinear(line.opcode) || OpcodeClasses.ShortBranching(line.opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object LinearOrLabel extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
line.opcode == Opcode.LABEL || OpcodeClasses.AllLinear(line.opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ReadsA extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.ReadsAAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ReadsAIfImplied(line.opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ReadsMemory extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
line.addrMode match {
|
||||||
|
case AddrMode.Indirect => true
|
||||||
|
case AddrMode.Implied | AddrMode.Immediate => false
|
||||||
|
case _ =>
|
||||||
|
OpcodeClasses.ReadsMemoryIfNotImpliedOrImmediate(line.opcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ReadsX extends AssemblyLinePattern {
|
||||||
|
val XAddrModes = Set(AddrMode.AbsoluteX, AddrMode.IndexedX, AddrMode.ZeroPageX, AddrMode.AbsoluteIndexedX)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.ReadsXAlways(line.opcode) || XAddrModes(line.addrMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ReadsY extends AssemblyLinePattern {
|
||||||
|
val YAddrModes = Set(AddrMode.AbsoluteY, AddrMode.IndexedY, AddrMode.ZeroPageY)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.ReadsYAlways(line.opcode) || YAddrModes(line.addrMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ConcernsC extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.ReadsC(line.opcode) && OpcodeClasses.ChangesC(line.opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ConcernsA extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.ConcernsAAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ConcernsAIfImplied(line.opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ConcernsX extends AssemblyLinePattern {
|
||||||
|
val XAddrModes = Set(AddrMode.AbsoluteX, AddrMode.IndexedX, AddrMode.ZeroPageX)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.ConcernsXAlways(line.opcode) || XAddrModes(line.addrMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ConcernsY extends AssemblyLinePattern {
|
||||||
|
val YAddrModes = Set(AddrMode.AbsoluteY, AddrMode.IndexedY, AddrMode.ZeroPageY)
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.ConcernsYAlways(line.opcode) || YAddrModes(line.addrMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ChangesA extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.ChangesAAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ChangesAIfImplied(line.opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ChangesMemory extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
OpcodeClasses.ChangesMemoryAlways(line.opcode) || line.addrMode != AddrMode.Implied && OpcodeClasses.ChangesMemoryIfNotImplied(line.opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DoesntChangeMemoryAt(addrMode1: Int, param1: Int) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = {
|
||||||
|
val p1 = ctx.get[Constant](param1)
|
||||||
|
val p2 = line.parameter
|
||||||
|
val a1 = ctx.get[AddrMode.Value](addrMode1)
|
||||||
|
val a2 = line.addrMode
|
||||||
|
val changesSomeMemory = OpcodeClasses.ChangesMemoryAlways(line.opcode) || line.addrMode != AddrMode.Implied && OpcodeClasses.ChangesMemoryIfNotImplied(line.opcode)
|
||||||
|
!changesSomeMemory || memoryAccessDoesntOverlap(a1, p1, a2, p2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ConcernsMemory extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
ReadsMemory.matchLineTo(ctx, flowInfo, line) && ChangesMemory.matchLineTo(ctx, flowInfo, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DoesNotConcernMemoryAt(addrMode1: Int, param1: Int) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = {
|
||||||
|
val p1 = ctx.get[Constant](param1)
|
||||||
|
val p2 = line.parameter
|
||||||
|
val a1 = ctx.get[AddrMode.Value](addrMode1)
|
||||||
|
val a2 = line.addrMode
|
||||||
|
memoryAccessDoesntOverlap(a1, p1, a2, p2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasOpcode(op: Opcode.Value) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
line.opcode == op
|
||||||
|
|
||||||
|
override def toString: String = op.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasOpcodeIn(ops: Set[Opcode.Value]) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
ops(line.opcode)
|
||||||
|
|
||||||
|
override def toString: String = ops.mkString("{", ",", "}")
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasAddrMode(am: AddrMode.Value) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
line.addrMode == am
|
||||||
|
|
||||||
|
override def toString: String = am.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasAddrModeIn(ams: Set[AddrMode.Value]) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
ams(line.addrMode)
|
||||||
|
|
||||||
|
override def toString: String = ams.mkString("{", ",", "}")
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HasImmediate(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
line.addrMode == AddrMode.Immediate && (line.parameter.quickSimplify match {
|
||||||
|
case NumericConstant(j, _) => (i & 0xff) == (j & 0xff)
|
||||||
|
case _ => false
|
||||||
|
})
|
||||||
|
|
||||||
|
override def toString: String = "#" + i
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MatchObject(i: Int, f: Function[AssemblyLine, Any]) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
ctx.addObject(i, f(line))
|
||||||
|
|
||||||
|
override def toString: String = s"(?<$i>...)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MatchParameter(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
ctx.addObject(i, line.parameter.quickSimplify)
|
||||||
|
|
||||||
|
override def toString: String = s"(?<$i>Param)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DontMatchParameter(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
ctx.dontMatch(i, line.parameter.quickSimplify)
|
||||||
|
|
||||||
|
override def toString: String = s"¬(?<$i>Param)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MatchAddrMode(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
ctx.addObject(i, line.addrMode)
|
||||||
|
|
||||||
|
override def toString: String = s"¬(?<$i>AddrMode)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MatchOpcode(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
ctx.addObject(i, line.opcode)
|
||||||
|
|
||||||
|
override def toString: String = s"¬(?<$i>Op)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MatchImmediate(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
if (line.addrMode == AddrMode.Immediate) {
|
||||||
|
ctx.addObject(i, line.parameter.quickSimplify)
|
||||||
|
} else false
|
||||||
|
|
||||||
|
override def toString: String = s"(?<$i>#)"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case class DoesntChangeIndexingInAddrMode(i: Int) extends AssemblyLinePattern {
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
|
||||||
|
ctx.get[AddrMode.Value](i) match {
|
||||||
|
case AddrMode.ZeroPageX | AddrMode.AbsoluteX | AddrMode.IndexedX | AddrMode.AbsoluteIndexedX => !OpcodeClasses.ChangesX.contains(line.opcode)
|
||||||
|
case AddrMode.ZeroPageY | AddrMode.AbsoluteY | AddrMode.IndexedY => !OpcodeClasses.ChangesY.contains(line.opcode)
|
||||||
|
case _ => true
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"¬(?<$i>AddrMode)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Before(pattern: AssemblyPattern) extends AssemblyLinePattern {
|
||||||
|
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = {
|
||||||
|
pattern.validate(needsFlowInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = code match {
|
||||||
|
case Nil => None
|
||||||
|
case x :: xs => pattern.matchTo(ctx, xs) match {
|
||||||
|
case Some(m) => Some(xs)
|
||||||
|
case None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = ???
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object SizeOptimizations {
|
||||||
|
|
||||||
|
}
|
75
src/main/scala/millfork/assembly/opt/SuperOptimizer.scala
Normal file
75
src/main/scala/millfork/assembly/opt/SuperOptimizer.scala
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.{CompilationFlag, CompilationOptions, OptimizationPresets}
|
||||||
|
import millfork.assembly.{AddrMode, AssemblyLine, Opcode}
|
||||||
|
import millfork.env.NormalFunction
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object SuperOptimizer extends AssemblyOptimization {
|
||||||
|
|
||||||
|
def optimize(m: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = {
|
||||||
|
val oldVerbosity = ErrorReporting.verbosity
|
||||||
|
ErrorReporting.verbosity = -1
|
||||||
|
var allOptimizers = OptimizationPresets.Good ++ LaterOptimizations.All
|
||||||
|
if (options.flag(CompilationFlag.EmitIllegals)) {
|
||||||
|
allOptimizers ++= UndocumentedOptimizations.All
|
||||||
|
}
|
||||||
|
if (options.flag(CompilationFlag.EmitCmosOpcodes)) {
|
||||||
|
allOptimizers ++= CmosOptimizations.All
|
||||||
|
}
|
||||||
|
allOptimizers ++= List(
|
||||||
|
VariableToRegisterOptimization,
|
||||||
|
ChangeIndexRegisterOptimizationPreferringX2Y,
|
||||||
|
ChangeIndexRegisterOptimizationPreferringY2X,
|
||||||
|
UnusedLabelRemoval)
|
||||||
|
val seenSoFar = mutable.Set[CodeView]()
|
||||||
|
val queue = mutable.Queue[(List[AssemblyOptimization], List[AssemblyLine])]()
|
||||||
|
val leaves = mutable.ListBuffer[(List[AssemblyOptimization], List[AssemblyLine])]()
|
||||||
|
seenSoFar += viewCode(code)
|
||||||
|
queue.enqueue(Nil -> code)
|
||||||
|
while(queue.nonEmpty) {
|
||||||
|
val (optsSoFar, codeSoFar) = queue.dequeue()
|
||||||
|
var isLeaf = true
|
||||||
|
allOptimizers.par.foreach { o =>
|
||||||
|
val optimized = o.optimize(m, codeSoFar, options)
|
||||||
|
val view = viewCode(optimized)
|
||||||
|
seenSoFar.synchronized{
|
||||||
|
if (!seenSoFar(view)) {
|
||||||
|
isLeaf = false
|
||||||
|
seenSoFar += view
|
||||||
|
queue.enqueue((o :: optsSoFar) -> optimized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isLeaf) {
|
||||||
|
// println(codeSoFar.map(_.sizeInBytes).sum + " B: " + optsSoFar.reverse.map(_.name).mkString(" -> "))
|
||||||
|
leaves += optsSoFar -> codeSoFar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = leaves.minBy(_._2.map(_.cost).sum)
|
||||||
|
ErrorReporting.verbosity = oldVerbosity
|
||||||
|
ErrorReporting.debug(s"Visited ${leaves.size} leaves")
|
||||||
|
ErrorReporting.debug(s"${code.map(_.sizeInBytes).sum} B -> ${result._2.map(_.sizeInBytes).sum} B: ${result._1.reverse.map(_.name).mkString(" -> ")}")
|
||||||
|
result._1.reverse.foldLeft(code){(c, opt) =>
|
||||||
|
val n = opt.optimize(m, c, options)
|
||||||
|
// println(c.mkString("","",""))
|
||||||
|
// println(n.mkString("","",""))
|
||||||
|
n
|
||||||
|
}
|
||||||
|
result._2
|
||||||
|
}
|
||||||
|
|
||||||
|
override val name = "Superoptimizer"
|
||||||
|
|
||||||
|
def viewCode(code: List[AssemblyLine]): CodeView = {
|
||||||
|
CodeView(code.map(l => l.opcode -> l.addrMode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class CodeView(content: List[(Opcode.Value, AddrMode.Value)])
|
|
@ -0,0 +1,340 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
import millfork.assembly.{AddrMode, AssemblyLine, Opcode, State}
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.assembly.OpcodeClasses._
|
||||||
|
import millfork.env.{Constant, NormalFunction, NumericConstant}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object UndocumentedOptimizations {
|
||||||
|
|
||||||
|
val counter = new AtomicInteger(30000)
|
||||||
|
|
||||||
|
def getNextLabel(prefix: String) = f".${prefix}%s__${counter.getAndIncrement()}%05d"
|
||||||
|
|
||||||
|
// TODO: test these
|
||||||
|
|
||||||
|
private val LaxAddrModeRestriction = Not(HasAddrModeIn(Set(AbsoluteX, ZeroPageX, IndexedX, Immediate)))
|
||||||
|
|
||||||
|
//noinspection ScalaUnnecessaryParentheses
|
||||||
|
val UseLax = new RuleBasedAssemblyOptimization("Using undocumented instruction LAX",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
|
||||||
|
(HasOpcode(LDA) & Elidable & MatchAddrMode(0) & MatchParameter(1) & LaxAddrModeRestriction) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsA) & Not(ChangesMemory) & Not(HasOpcode(LDX))).*.capture(2) ~
|
||||||
|
(HasOpcode(LDX) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](2) :+ code.head.copy(opcode = LAX)
|
||||||
|
},
|
||||||
|
(HasOpcode(LDX) & Elidable & MatchAddrMode(0) & MatchParameter(1) & LaxAddrModeRestriction) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsX) & Not(ChangesMemory) & Not(HasOpcode(LDA))).*.capture(2) ~
|
||||||
|
(HasOpcode(LDA) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](2) :+ code.head.copy(opcode = LAX)
|
||||||
|
},
|
||||||
|
|
||||||
|
(HasOpcode(LDA) & Elidable & LaxAddrModeRestriction) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsA) & Not(ChangesMemory) & Not(HasOpcode(TAX))).*.capture(2) ~
|
||||||
|
(HasOpcode(TAX) & Elidable) ~~> { (code, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](2) :+ code.head.copy(opcode = LAX)
|
||||||
|
},
|
||||||
|
(HasOpcode(LDX) & Elidable & LaxAddrModeRestriction) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsX) & Not(ChangesMemory) & Not(HasOpcode(TXA))).*.capture(2) ~
|
||||||
|
(HasOpcode(TXA) & Elidable) ~~> { (code, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](2) :+ code.head.copy(opcode = LAX)
|
||||||
|
},
|
||||||
|
|
||||||
|
(HasOpcode(LDA) & Elidable & MatchAddrMode(0) & MatchParameter(1) & LaxAddrModeRestriction) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsX) & Not(ChangesA) & Not(ChangesMemory) & Not(HasOpcode(LDX))).*.capture(2) ~
|
||||||
|
(HasOpcode(LDX) & Elidable & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> { (code, ctx) =>
|
||||||
|
code.head.copy(opcode = LAX) :: ctx.get[List[AssemblyLine]](2)
|
||||||
|
},
|
||||||
|
(HasOpcode(LDX) & Elidable & MatchAddrMode(0) & MatchParameter(1) & LaxAddrModeRestriction) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsA) & Not(ChangesX) & Not(ChangesMemory) & Not(HasOpcode(LDA))).*.capture(2) ~
|
||||||
|
(HasOpcode(LDA) & Elidable & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> { (code, ctx) =>
|
||||||
|
code.head.copy(opcode = LAX) :: ctx.get[List[AssemblyLine]](2)
|
||||||
|
},
|
||||||
|
|
||||||
|
(HasOpcode(LDA) & Elidable & LaxAddrModeRestriction) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsX) & Not(ChangesA) & Not(ChangesMemory) & Not(HasOpcode(TAX))).*.capture(2) ~
|
||||||
|
(HasOpcode(TAX) & Elidable & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> { (code, ctx) =>
|
||||||
|
code.head.copy(opcode = LAX) :: ctx.get[List[AssemblyLine]](2)
|
||||||
|
},
|
||||||
|
(HasOpcode(LDX) & Elidable & LaxAddrModeRestriction) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsA) & Not(ChangesX) & Not(ChangesMemory) & Not(HasOpcode(TXA))).*.capture(2) ~
|
||||||
|
(HasOpcode(TXA) & Elidable & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> { (code, ctx) =>
|
||||||
|
code.head.copy(opcode = LAX) :: ctx.get[List[AssemblyLine]](2)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val SaxModes: Set[AddrMode.Value] = Set(ZeroPage, IndexedX, ZeroPageY, Absolute)
|
||||||
|
|
||||||
|
val UseSax = new RuleBasedAssemblyOptimization("Using undocumented instruction SAX",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ConcernsA) & Not(ConcernsX)).?.capture(10) ~
|
||||||
|
(HasOpcode(AND) & Elidable & MatchAddrMode(2) & MatchParameter(3) & Not(ReadsX)) ~
|
||||||
|
(Linear & Not(ConcernsA) & Not(ConcernsX)).?.capture(11) ~
|
||||||
|
(HasOpcode(STA) & Elidable & MatchAddrMode(4) & MatchParameter(5) & HasAddrModeIn(SaxModes) & DontMatchParameter(0)) ~
|
||||||
|
(Linear & Not(ConcernsA) & Not(ConcernsX) & Not(ChangesMemory)).?.capture(12) ~
|
||||||
|
(HasOpcode(LDA) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(LinearOrLabel & Not(ConcernsX)).*.capture(13) ~ OverwritesX ~~> { (code, ctx) =>
|
||||||
|
val lda = code.head
|
||||||
|
val ldx = AssemblyLine(LDX, ctx.get[AddrMode.Value](2), ctx.get[Constant](3))
|
||||||
|
val sax = AssemblyLine(SAX, ctx.get[AddrMode.Value](4), ctx.get[Constant](5))
|
||||||
|
val fragment0 = lda :: ctx.get[List[AssemblyLine]](10)
|
||||||
|
val fragment1 = ldx :: ctx.get[List[AssemblyLine]](11)
|
||||||
|
val fragment2 = sax :: ctx.get[List[AssemblyLine]](12)
|
||||||
|
val fragment3 = ctx.get[List[AssemblyLine]](13)
|
||||||
|
List(fragment0, fragment1, fragment2, fragment3).flatten
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def andConstant(const: Constant, mask: Int): Option[Long] = const match {
|
||||||
|
case NumericConstant(n, _) => Some(n & mask)
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
|
||||||
|
val UseAnc = new RuleBasedAssemblyOptimization("Using undocumented instruction ANC",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BothFlows,
|
||||||
|
(Elidable & HasOpcode(LDA) & HasImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(CLC)) ~~> (_ => List(AssemblyLine.immediate(ANC, 0))),
|
||||||
|
(Elidable & HasOpcode(LDA) & HasImmediate(0) & HasClear(State.C)) ~~> (_ => List(AssemblyLine.immediate(ANC, 0))),
|
||||||
|
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
|
||||||
|
Where(c => andConstant(c.get[Constant](0), 0x80).contains(0)) ~
|
||||||
|
(Elidable & HasOpcode(CLC)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
|
||||||
|
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
|
||||||
|
Where(c => andConstant(c.get[Constant](0), 0x80).contains(0x80)) ~
|
||||||
|
(Elidable & HasOpcode(SEC)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
|
||||||
|
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(CMP) & HasImmediate(0x80) & DoesntMatterWhatItDoesWith(State.Z, State.N)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
|
||||||
|
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(CMP) & HasImmediate(0x80) & DoesntMatterWhatItDoesWith(State.Z, State.N)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
|
||||||
|
(Elidable & HasOpcode(AND) & MatchImmediate(0) & HasClear(State.C)) ~
|
||||||
|
Where(c => andConstant(c.get[Constant](0), 0x80).contains(0)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
|
||||||
|
(Elidable & HasOpcode(AND) & MatchImmediate(0) & HasSet(State.C)) ~
|
||||||
|
Where(c => andConstant(c.get[Constant](0), 0x80).contains(0)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
|
||||||
|
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcodeIn(Set(ROL, ASL)) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.Z, State.N, State.A)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseSbx = new RuleBasedAssemblyOptimization("Using undocumented instruction SBX",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BothFlows,
|
||||||
|
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.A, State.C)).+.captureLength(0) ~
|
||||||
|
Where(_.get[Int](0) > 2) ~~> ((_, ctx) => List(
|
||||||
|
AssemblyLine.implied(TXA),
|
||||||
|
AssemblyLine.immediate(SBX, ctx.get[Int](0)),
|
||||||
|
)),
|
||||||
|
(Elidable & HasOpcode(INX) & DoesntMatterWhatItDoesWith(State.A, State.C)).+.captureLength(0) ~
|
||||||
|
Where(_.get[Int](0) > 2) ~~> ((_, ctx) => List(
|
||||||
|
AssemblyLine.implied(TXA),
|
||||||
|
AssemblyLine.immediate(SBX, 256 - ctx.get[Int](0)),
|
||||||
|
)),
|
||||||
|
HasOpcode(TXA) ~
|
||||||
|
(Elidable & HasOpcode(CLC)).? ~
|
||||||
|
(Elidable & HasClear(State.C) & HasClear(State.D) & HasOpcode(ADC) & MatchImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.C, State.A)) ~~> ((code, ctx) => List(
|
||||||
|
code.head,
|
||||||
|
AssemblyLine.immediate(SBX, 256 - ctx.get[Int](0)),
|
||||||
|
)),
|
||||||
|
HasOpcode(TXA) ~
|
||||||
|
(Elidable & HasOpcode(SEC)).? ~
|
||||||
|
(Elidable & HasSet(State.C) & HasClear(State.D) & HasOpcode(SBC) & MatchImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.C, State.A)) ~~> ((code, ctx) => List(
|
||||||
|
code.head,
|
||||||
|
AssemblyLine.immediate(SBX, ctx.get[Int](0)),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val UseAlr = new RuleBasedAssemblyOptimization("Using undocumented instruction ALR",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
(Elidable & HasOpcode(AND) & HasAddrMode(Immediate)) ~
|
||||||
|
(Elidable & HasOpcode(LSR) & HasAddrMode(Implied)) ~~> { (code, ctx) =>
|
||||||
|
List(AssemblyLine.immediate(ALR, code.head.parameter))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LSR) & HasAddrMode(Implied)) ~
|
||||||
|
(Elidable & HasOpcode(CLC)) ~~> { (code, ctx) =>
|
||||||
|
List(AssemblyLine.immediate(ALR, 0xFE))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseArr = new RuleBasedAssemblyOptimization("Using undocumented instruction ARR",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BothFlows,
|
||||||
|
(HasClear(State.D) & Elidable & HasOpcode(AND) & HasAddrMode(Immediate)) ~
|
||||||
|
(Elidable & HasOpcode(ROR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> { (code, ctx) =>
|
||||||
|
List(AssemblyLine.immediate(ARR, code.head.parameter))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
private def trivialSequence1(o1: Opcode.Value, o2: Opcode.Value, extra: AssemblyLinePattern, combined: Opcode.Value) =
|
||||||
|
(Elidable & HasOpcode(o1) & HasAddrMode(Absolute) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & DoesNotConcernMemoryAt(0, 1) & extra).* ~
|
||||||
|
(Elidable & HasOpcode(o2) & HasAddrMode(Absolute) & MatchParameter(1)) ~~> { (code, ctx) =>
|
||||||
|
code.tail.init :+ AssemblyLine(combined, Absolute, ctx.get[Constant](1))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def trivialSequence2(o1: Opcode.Value, o2: Opcode.Value, extra: AssemblyLinePattern, combined: Opcode.Value) =
|
||||||
|
(Elidable & HasOpcode(o1) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & DoesNotConcernMemoryAt(0, 1) & extra).* ~
|
||||||
|
(Elidable & HasOpcode(o2) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
|
||||||
|
code.tail.init :+ AssemblyLine(combined, ctx.get[AddrMode.Value](0), ctx.get[Constant](1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROL c LDA c AND d => LDA d RLA c
|
||||||
|
private def trivialCommutativeSequence(o1: Opcode.Value, o2: Opcode.Value, combined: Opcode.Value) = {
|
||||||
|
(Elidable & HasOpcode(o1) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Elidable & HasOpcode(LDA) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Elidable & HasOpcode(o2) & MatchAddrMode(2) & MatchParameter(3)) ~~> { code =>
|
||||||
|
List(code(2).copy(opcode = LDA), code(1).copy(opcode = combined))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val UseSlo = new RuleBasedAssemblyOptimization("Using undocumented instruction SLO",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
trivialSequence1(ASL, ORA, Not(ConcernsC), SLO),
|
||||||
|
trivialSequence2(ASL, ORA, Not(ConcernsC), SLO),
|
||||||
|
trivialCommutativeSequence(ASL, ORA, SLO),
|
||||||
|
(Elidable & HasOpcode(ASL) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ConcernsMemory)).* ~
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
|
||||||
|
code.tail.init ++ List(AssemblyLine.immediate(LDA, 0), AssemblyLine(SLO, ctx.get[AddrMode.Value](0), ctx.get[Constant](1)))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ConcernsMemory) & Not(ChangesA)).*.capture(2) ~
|
||||||
|
(Elidable & HasOpcode(ASL) & HasAddrMode(Implied)) ~
|
||||||
|
(Linear & Not(ConcernsMemory) & Not(ChangesA) & Not(ReadsC) & Not(ReadsNOrZ)).*.capture(3) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
|
||||||
|
List(AssemblyLine.immediate(LDA, 0), AssemblyLine(SRE, ctx.get[AddrMode.Value](0), ctx.get[Constant](1))) ++
|
||||||
|
ctx.get[List[AssemblyLine]](2) ++
|
||||||
|
ctx.get[List[AssemblyLine]](3)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseSre = new RuleBasedAssemblyOptimization("Using undocumented instruction SRE",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
trivialSequence1(LSR, EOR, Not(ConcernsC), SRE),
|
||||||
|
trivialSequence2(LSR, EOR, Not(ConcernsC), SRE),
|
||||||
|
trivialCommutativeSequence(LSR, EOR, SRE),
|
||||||
|
(Elidable & HasOpcode(LSR) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ConcernsMemory)).* ~
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
|
||||||
|
code.tail.init ++ List(AssemblyLine.immediate(LDA, 0), AssemblyLine(SRE, ctx.get[AddrMode.Value](0), ctx.get[Constant](1)))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Linear & Not(ConcernsMemory) & Not(ChangesA)).*.capture(2) ~
|
||||||
|
(Elidable & HasOpcode(LSR) & HasAddrMode(Implied)) ~
|
||||||
|
(Linear & Not(ConcernsMemory) & Not(ChangesA) & Not(ReadsC) & Not(ReadsNOrZ)).*.capture(3) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
|
||||||
|
List(AssemblyLine.immediate(LDA, 0), AssemblyLine(SRE, ctx.get[AddrMode.Value](0), ctx.get[Constant](1))) ++
|
||||||
|
ctx.get[List[AssemblyLine]](2) ++
|
||||||
|
ctx.get[List[AssemblyLine]](3)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseRla = new RuleBasedAssemblyOptimization("Using undocumented instruction RLA",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
trivialSequence1(ROL, AND, Not(ConcernsC), RLA),
|
||||||
|
trivialSequence2(ROL, AND, Not(ConcernsC), RLA),
|
||||||
|
trivialCommutativeSequence(ROL, AND, RLA),
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseRra = new RuleBasedAssemblyOptimization("Using undocumented instruction RRA",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
// TODO: is it ok? carry flag and stuff?
|
||||||
|
trivialSequence1(ROR, ADC, Not(ConcernsC), RRA),
|
||||||
|
trivialSequence2(ROR, ADC, Not(ConcernsC), RRA),
|
||||||
|
trivialCommutativeSequence(ROR, ADC, RRA),
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseDcp = new RuleBasedAssemblyOptimization("Using undocumented instruction DCP",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BothFlows,
|
||||||
|
trivialSequence1(DEC, CMP, Not(ConcernsC), DCP),
|
||||||
|
trivialSequence2(DEC, CMP, Not(ConcernsC), DCP),
|
||||||
|
(Elidable & HasOpcode(LDA) & HasAddrModeIn(Set(IndexedX, ZeroPageX, AbsoluteX))) ~
|
||||||
|
(Elidable & HasOpcode(TAX)) ~
|
||||||
|
(Elidable & HasOpcode(DEC) & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.A, State.Y, State.X, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(code.head.copy(opcode = LDY), code.last.copy(opcode = DCP, addrMode = AbsoluteY))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(DEC) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Elidable & HasOpcode(LDA) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Elidable & HasOpcode(CMP) & MatchAddrMode(2) & MatchParameter(3) & DoesntMatterWhatItDoesWith(State.V, State.C, State.N, State.A)) ~~> { code =>
|
||||||
|
List(code(2).copy(opcode = LDA), code(1).copy(opcode = DCP))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val UseIsc = new RuleBasedAssemblyOptimization("Using undocumented instruction ISC",
|
||||||
|
needsFlowInfo = FlowInfoRequirement.BothFlows,
|
||||||
|
trivialSequence1(INC, SBC, Not(ReadsC), ISC),
|
||||||
|
trivialSequence2(INC, SBC, Not(ReadsC), ISC),
|
||||||
|
(Elidable & HasOpcode(LDA) & HasImmediate(0) & HasClear(State.D)) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(IndexedX, IndexedY, AbsoluteY))) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
val label = getNextLabel("is")
|
||||||
|
List(
|
||||||
|
AssemblyLine.relative(BCC, label),
|
||||||
|
code.last.copy(opcode = ISC),
|
||||||
|
AssemblyLine.label(label))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(IndexedX, IndexedY, AbsoluteY))) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & HasImmediate(0) & HasClear(State.D)) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
val label = getNextLabel("is")
|
||||||
|
List(
|
||||||
|
AssemblyLine.relative(BCC, label),
|
||||||
|
code.last.copy(opcode = ISC),
|
||||||
|
AssemblyLine.label(label))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(CLC)).? ~
|
||||||
|
(Elidable & HasOpcode(LDA) & HasImmediate(1) & HasClear(State.D) & HasClear(State.C)) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(IndexedX, IndexedY, AbsoluteY))) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(code.last.copy(opcode = ISC))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(CLC)).? ~
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(1) & HasClear(State.D) & HasClear(State.C) & MatchAddrMode(2) & HasAddrModeIn(Set(IndexedX, IndexedY, AbsoluteY))) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & HasImmediate(1)) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchAddrMode(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(code.last.copy(opcode = ISC))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(SEC)).? ~
|
||||||
|
(Elidable & HasOpcode(LDA) & HasImmediate(0) & HasClear(State.D) & HasSet(State.C)) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(IndexedX, IndexedY, AbsoluteY))) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(code.last.copy(opcode = ISC))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(SEC)).? ~
|
||||||
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(1) & HasClear(State.D) & HasSet(State.C) & MatchAddrMode(2) & HasAddrModeIn(Set(IndexedX, IndexedY, AbsoluteY))) ~
|
||||||
|
(Elidable & HasOpcode(ADC) & HasImmediate(0)) ~
|
||||||
|
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchAddrMode(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(code.last.copy(opcode = ISC))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(LDA) & HasAddrModeIn(Set(IndexedX, ZeroPageX, AbsoluteX))) ~
|
||||||
|
(Elidable & HasOpcode(TAX)) ~
|
||||||
|
(Elidable & HasOpcode(INC) & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.A, State.Y, State.X, State.C, State.Z, State.N, State.V)) ~~> { code =>
|
||||||
|
List(code.head.copy(opcode = LDY), code.last.copy(opcode = ISC, addrMode = AbsoluteY))
|
||||||
|
},
|
||||||
|
(Elidable & HasOpcode(INC) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Elidable & HasOpcode(LDA) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
|
(Elidable & HasOpcode(CMP) & HasClear(State.D) & MatchAddrMode(2) & MatchParameter(3) & DoesntMatterWhatItDoesWith(State.V, State.C, State.N, State.A)) ~~> { code =>
|
||||||
|
List(code(2).copy(opcode = LDA), AssemblyLine.implied(SEC), code(1).copy(opcode = ISC))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val All: List[AssemblyOptimization] = List(
|
||||||
|
UseLax,
|
||||||
|
UseSax,
|
||||||
|
UseSbx,
|
||||||
|
UseAnc,
|
||||||
|
UseSlo,
|
||||||
|
UseSre,
|
||||||
|
UseAlr,
|
||||||
|
UseArr,
|
||||||
|
UseRla,
|
||||||
|
UseRra,
|
||||||
|
UseIsc,
|
||||||
|
UseDcp,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.CompilationOptions
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.{AddrMode, AssemblyLine}
|
||||||
|
import millfork.env._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object UnusedLabelRemoval extends AssemblyOptimization {
|
||||||
|
|
||||||
|
override def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = {
|
||||||
|
val usedLabels = code.flatMap {
|
||||||
|
case AssemblyLine(LABEL, _, _, _) => None
|
||||||
|
case AssemblyLine(_, _, MemoryAddressConstant(Label(l)), _) => Some(l)
|
||||||
|
case _ => None
|
||||||
|
}.toSet
|
||||||
|
val definedLabels = code.flatMap {
|
||||||
|
case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(l)), _) => Some(l).filter(_.startsWith("."))
|
||||||
|
case _ => None
|
||||||
|
}.toSet
|
||||||
|
val toRemove = definedLabels -- usedLabels
|
||||||
|
if (toRemove.nonEmpty) {
|
||||||
|
ErrorReporting.debug("Removing labels: " + toRemove.mkString(", "))
|
||||||
|
code.filterNot {
|
||||||
|
case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(l)), _) => toRemove(l)
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def name = "Unused label removal"
|
||||||
|
}
|
|
@ -0,0 +1,322 @@
|
||||||
|
package millfork.assembly.opt
|
||||||
|
|
||||||
|
import millfork.CompilationOptions
|
||||||
|
import millfork.assembly.{AddrMode, AssemblyLine}
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.env._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object VariableToRegisterOptimization extends AssemblyOptimization {
|
||||||
|
|
||||||
|
// If any of these opcodes is present within a method,
|
||||||
|
// then it's too hard to assign any variable to a register.
|
||||||
|
private val opcodesThatAlwaysPrecludeXAllocation = Set(JSR, STX, TXA, PHX, PLX, INX, DEX, CPX, SBX, SAX)
|
||||||
|
|
||||||
|
private val opcodesThatAlwaysPrecludeYAllocation = Set(JSR, STY, TYA, PHY, PLY, INY, DEY, CPY)
|
||||||
|
|
||||||
|
// If any of these opcodes is used on a variable
|
||||||
|
// then it's too hard to assign that variable to a register.
|
||||||
|
// Also, LDY prevents assigning a variable to X and LDX prevents assigning a variable to Y.
|
||||||
|
private val opcodesThatCannotBeUsedWithIndexRegistersAsParameters =
|
||||||
|
Set(EOR, ORA, AND, BIT, ADC, SBC, CMP, CPX, CPY, STY, STX)
|
||||||
|
|
||||||
|
override def name = "Allocating variables to index registers"
|
||||||
|
|
||||||
|
|
||||||
|
override def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = {
|
||||||
|
val paramVariables = f.params match {
|
||||||
|
case NormalParamSignature(ps) =>
|
||||||
|
ps.map(_.name).toSet
|
||||||
|
case _ =>
|
||||||
|
// assembly functions do not get this optimization
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
val stillUsedVariables = code.flatMap {
|
||||||
|
case AssemblyLine(_, _, MemoryAddressConstant(th), _) => Some(th.name)
|
||||||
|
case _ => None
|
||||||
|
}.toSet
|
||||||
|
val localVariables = f.environment.getAllLocalVariables.filter {
|
||||||
|
case MemoryVariable(name, typ, VariableAllocationMethod.Auto) =>
|
||||||
|
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name)
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
val candidates = None :: localVariables.map(v => Option(v.name))
|
||||||
|
|
||||||
|
val variants = for {
|
||||||
|
vx <- candidates.par
|
||||||
|
vy <- candidates
|
||||||
|
if vx != vy
|
||||||
|
(score, prologueLength) <- canBeInlined(vx, vy, code.tail, Some(1))
|
||||||
|
if prologueLength >= 1
|
||||||
|
} yield (score, prologueLength, vx, vy)
|
||||||
|
|
||||||
|
if (variants.isEmpty) {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
val (_, bestPrologueLength, bestX, bestY) = variants.max
|
||||||
|
|
||||||
|
if ((bestX.isDefined || bestY.isDefined) && bestPrologueLength != 0xffff) {
|
||||||
|
(bestX, bestY) match {
|
||||||
|
case (Some(x), Some(y)) => ErrorReporting.debug(s"Inlining $x to X and $y to Y")
|
||||||
|
case (Some(x), None) => ErrorReporting.debug(s"Inlining $x to X")
|
||||||
|
case (None, Some(y)) => ErrorReporting.debug(s"Inlining $y to Y")
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
bestX.foreach(f.environment.removeVariable)
|
||||||
|
bestY.foreach(f.environment.removeVariable)
|
||||||
|
code.take(bestPrologueLength) ++ inlineVars(bestX, bestY, code.drop(bestPrologueLength))
|
||||||
|
} else {
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private def add(i: Int) = (p: (Int, Int)) => (p._1 + i) -> p._2
|
||||||
|
|
||||||
|
private def mark(i: Option[Int]) = (p: (Int, Int)) => p._1 -> i.getOrElse(p._2)
|
||||||
|
|
||||||
|
def canBeInlined(xCandidate: Option[String], yCandidate: Option[String], lines: List[AssemblyLine], instrCounter: Option[Int]): Option[(Int, Int)] = {
|
||||||
|
val vx = xCandidate.getOrElse("-")
|
||||||
|
val vy = yCandidate.getOrElse("-")
|
||||||
|
val next = instrCounter.map(_ + 1)
|
||||||
|
val next2 = instrCounter.map(_ + 2)
|
||||||
|
lines match {
|
||||||
|
case AssemblyLine(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _), _) :: xs
|
||||||
|
if th.name == vx || th.name == vy =>
|
||||||
|
// if an address of a variable is used, then that variable cannot be assigned to a register
|
||||||
|
None
|
||||||
|
case AssemblyLine(_, Immediate, HalfWordConstant(MemoryAddressConstant(th), _), _) :: xs
|
||||||
|
if th.name == vx || th.name == vy =>
|
||||||
|
// if an address of a variable is used, then that variable cannot be assigned to a register
|
||||||
|
None
|
||||||
|
|
||||||
|
case AssemblyLine(_, AbsoluteX | AbsoluteY | ZeroPageX | ZeroPageY, MemoryAddressConstant(th), _) :: xs =>
|
||||||
|
// if a variable is used as an array, then it cannot be assigned to a register
|
||||||
|
if (th.name == vx || th.name == vy) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
case AssemblyLine(opcode, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vx && (opcode == LDY || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
|
||||||
|
// if a variable is used by some opcodes, then it cannot be assigned to a register
|
||||||
|
None
|
||||||
|
|
||||||
|
case AssemblyLine(opcode, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vy && (opcode == LDX || opcode == LAX || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
|
||||||
|
// if a variable is used by some opcodes, then it cannot be assigned to a register
|
||||||
|
None
|
||||||
|
|
||||||
|
case AssemblyLine(LDX, Absolute, MemoryAddressConstant(th), elidable) :: xs
|
||||||
|
if xCandidate.isDefined =>
|
||||||
|
// if a register is populated with a different variable, then this variable cannot be assigned to that register
|
||||||
|
// removing LDX saves 3 cycles
|
||||||
|
if (elidable && th.name == vx) {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, None).map(add(3)).map(mark(instrCounter))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
case AssemblyLine(LAX, Absolute, MemoryAddressConstant(th), elidable) :: xs
|
||||||
|
if xCandidate.isDefined =>
|
||||||
|
// LAX = LDX-LDA, and since LDX simplifies to nothing and LDA simplifies to TXA,
|
||||||
|
// LAX simplifies to TXA, saving two bytes
|
||||||
|
if (elidable && th.name == vx) {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, None).map(add(2)).map(mark(instrCounter))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
case AssemblyLine(LDY, Absolute, MemoryAddressConstant(th), elidable) :: xs if yCandidate.isDefined =>
|
||||||
|
// if a register is populated with a different variable, then this variable cannot be assigned to that register
|
||||||
|
// removing LDX saves 3 cycles
|
||||||
|
if (elidable && th.name == vy) {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, None).map(add(3)).map(mark(instrCounter))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
case AssemblyLine(LDX, _, _, _) :: xs if xCandidate.isDefined =>
|
||||||
|
// if a register is populated with something else than a variable, then no variable cannot be assigned to that register
|
||||||
|
None
|
||||||
|
|
||||||
|
case AssemblyLine(LDY, _, _, _) :: xs if yCandidate.isDefined =>
|
||||||
|
// if a register is populated with something else than a variable, then no variable cannot be assigned to that register
|
||||||
|
None
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, Absolute, MemoryAddressConstant(th), elidable) :: AssemblyLine(TAX, _, _, elidable2) :: xs
|
||||||
|
if xCandidate.isDefined =>
|
||||||
|
// a variable cannot be inlined if there is TAX not after LDA of that variable
|
||||||
|
// but LDA-TAX can be simplified to TXA
|
||||||
|
if (elidable && elidable2 && th.name == vx) {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, None).map(add(3)).map(mark(instrCounter))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, Absolute, MemoryAddressConstant(th), elidable) :: AssemblyLine(TAY, _, _, elidable2) :: xs
|
||||||
|
if yCandidate.isDefined =>
|
||||||
|
// a variable cannot be inlined if there is TAY not after LDA of that variable
|
||||||
|
// but LDA-TAY can be simplified to TYA
|
||||||
|
if (elidable && elidable2 && th.name == vy) {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, None).map(add(3)).map(mark(instrCounter))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
case AssemblyLine(LDA | STA | INC | DEC, Absolute, MemoryAddressConstant(th), elidable) :: xs =>
|
||||||
|
// changing LDA->TXA, STA->TAX, INC->INX, DEC->DEX saves 2 cycles
|
||||||
|
if (th.name == vy || th.name == vx) {
|
||||||
|
if (elidable) canBeInlined(xCandidate, yCandidate, xs, None).map(add(2)).map(mark(instrCounter))
|
||||||
|
else None
|
||||||
|
} else {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
case AssemblyLine(TAX, _, _, _) :: xs if xCandidate.isDefined =>
|
||||||
|
// a variable cannot be inlined if there is TAX not after LDA of that variable
|
||||||
|
if (instrCounter.isDefined) {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, next)
|
||||||
|
} else None
|
||||||
|
|
||||||
|
case AssemblyLine(TAY, _, _, _) :: xs if yCandidate.isDefined =>
|
||||||
|
// a variable cannot be inlined if there is TAY not after LDA of that variable
|
||||||
|
if (instrCounter.isDefined) {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, next)
|
||||||
|
} else None
|
||||||
|
|
||||||
|
case AssemblyLine(LABEL, _, _, _) :: xs =>
|
||||||
|
// labels always end the initial section
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, None).map(mark(instrCounter))
|
||||||
|
|
||||||
|
case x :: xs =>
|
||||||
|
if (instrCounter.isDefined) {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, next)
|
||||||
|
} else {
|
||||||
|
if (xCandidate.isDefined && opcodesThatAlwaysPrecludeXAllocation(x.opcode)) {
|
||||||
|
None
|
||||||
|
} else if (yCandidate.isDefined && opcodesThatAlwaysPrecludeYAllocation(x.opcode)) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
canBeInlined(xCandidate, yCandidate, xs, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case Nil => Some(0 -> -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def inlineVars(xCandidate: Option[String], yCandidate: Option[String], lines: List[AssemblyLine]): List[AssemblyLine] = {
|
||||||
|
val vx = xCandidate.getOrElse("-")
|
||||||
|
val vy = yCandidate.getOrElse("-")
|
||||||
|
lines match {
|
||||||
|
case AssemblyLine(INC, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vx =>
|
||||||
|
AssemblyLine.implied(INX) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(INC, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vy =>
|
||||||
|
AssemblyLine.implied(INY) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(DEC, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vx =>
|
||||||
|
AssemblyLine.implied(DEX) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(DEC, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vy =>
|
||||||
|
AssemblyLine.implied(DEY) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDX, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vx =>
|
||||||
|
inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LAX, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vx =>
|
||||||
|
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDY, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vy =>
|
||||||
|
inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, Absolute, MemoryAddressConstant(th), true) :: AssemblyLine(TAX, _, _, true) :: xs
|
||||||
|
if th.name == vx =>
|
||||||
|
// these TXA's may get optimized away by a different optimization
|
||||||
|
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, Absolute, MemoryAddressConstant(th), true) :: AssemblyLine(TAY, _, _, true) :: xs
|
||||||
|
if th.name == vy =>
|
||||||
|
// these TYA's may get optimized away by a different optimization
|
||||||
|
AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, am, param, true) :: AssemblyLine(STA, Absolute, MemoryAddressConstant(th), true) :: xs
|
||||||
|
if th.name == vx && doesntUseX(am) =>
|
||||||
|
// these TXA's may get optimized away by a different optimization
|
||||||
|
AssemblyLine(LDX, am, param) :: AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, am, param, true) :: AssemblyLine(STA, Absolute, MemoryAddressConstant(th), true) :: xs
|
||||||
|
if th.name == vy && doesntUseY(am) =>
|
||||||
|
// these TYA's may get optimized away by a different optimization
|
||||||
|
AssemblyLine(LDY, am, param) :: AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, Absolute, MemoryAddressConstant(th), _) :: AssemblyLine(CMP, am, param, true) :: xs
|
||||||
|
if th.name == vx && doesntUseXOrY(am) =>
|
||||||
|
// ditto
|
||||||
|
AssemblyLine.implied(TXA) :: AssemblyLine(CPX, am, param) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, Absolute, MemoryAddressConstant(th), _) :: AssemblyLine(CMP, am, param, true) :: xs
|
||||||
|
if th.name == vy && doesntUseXOrY(am) =>
|
||||||
|
// ditto
|
||||||
|
AssemblyLine.implied(TYA) :: AssemblyLine(CPY, am, param) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vx =>
|
||||||
|
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(LDA, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vy =>
|
||||||
|
AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(STA, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vx =>
|
||||||
|
AssemblyLine.implied(TAX) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(STA, Absolute, MemoryAddressConstant(th), _) :: xs
|
||||||
|
if th.name == vy =>
|
||||||
|
AssemblyLine.implied(TAY) :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case AssemblyLine(TAX, _, _, _) :: xs if xCandidate.isDefined =>
|
||||||
|
ErrorReporting.fatal("Unexpected TAX")
|
||||||
|
|
||||||
|
case AssemblyLine(TAY, _, _, _) :: xs if yCandidate.isDefined =>
|
||||||
|
ErrorReporting.fatal("Unexpected TAY")
|
||||||
|
|
||||||
|
case x :: xs => x :: inlineVars(xCandidate, yCandidate, xs)
|
||||||
|
|
||||||
|
case Nil => Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def doesntUseY(am: AddrMode.Value): Boolean = am match {
|
||||||
|
case AbsoluteY | ZeroPageY | IndexedY => false
|
||||||
|
case _ => true
|
||||||
|
}
|
||||||
|
|
||||||
|
def doesntUseX(am: AddrMode.Value): Boolean = am match {
|
||||||
|
case AbsoluteX | ZeroPageX | IndexedX => false
|
||||||
|
case _ => true
|
||||||
|
}
|
||||||
|
|
||||||
|
def doesntUseXOrY(am: AddrMode.Value): Boolean = am match {
|
||||||
|
case Immediate | ZeroPage | Absolute | Relative | Indirect => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
201
src/main/scala/millfork/cli/CliOption.scala
Normal file
201
src/main/scala/millfork/cli/CliOption.scala
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
package millfork.cli
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
trait CliOption[T, O <: CliOption[T, O]] {
|
||||||
|
this: O =>
|
||||||
|
def toStrings(firstTab: Int): List[String] = {
|
||||||
|
val fl = firstLine
|
||||||
|
if (_description == "") {
|
||||||
|
List(fl)
|
||||||
|
} else if (fl.length < firstTab) {
|
||||||
|
List(fl.padTo(firstTab, ' ') + _description)
|
||||||
|
} else {
|
||||||
|
List(fl, "".padTo(firstTab, ' ') + _description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def firstLine: String = names.mkString(" | ")
|
||||||
|
|
||||||
|
def names: Seq[String]
|
||||||
|
|
||||||
|
private[cli] def length: Int
|
||||||
|
|
||||||
|
private[cli] val _shortName: String
|
||||||
|
private[cli] var _description: String = ""
|
||||||
|
private[cli] var _hidden = false
|
||||||
|
private[cli] var _maxEncounters = 1
|
||||||
|
private[cli] var _minEncounters = 0
|
||||||
|
private[cli] var _actualEncounters = 0
|
||||||
|
private[cli] var _onTooFew: Option[Int => Unit] = None
|
||||||
|
private[cli] var _onTooMany: Option[Int => Unit] = None
|
||||||
|
|
||||||
|
def validate(): Boolean = {
|
||||||
|
var ok = true
|
||||||
|
if (_actualEncounters < _minEncounters) {
|
||||||
|
_onTooFew.fold(throw new IllegalArgumentException(s"Too few ${_shortName} options: required ${_minEncounters}, given ${_actualEncounters}"))(_ (_actualEncounters))
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
if (_actualEncounters > _maxEncounters) {
|
||||||
|
_onTooMany.fold()(_ (_actualEncounters))
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
|
||||||
|
def onWrongNumber(action: Int => Unit): Unit = {
|
||||||
|
_onTooFew = Some(action)
|
||||||
|
_onTooMany = Some(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
def onTooFew(action: Int => Unit): Unit = {
|
||||||
|
_onTooFew = Some(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
def onTooMany(action: Int => Unit): Unit = {
|
||||||
|
_onTooMany = Some(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
def encounter(): Unit = {
|
||||||
|
_actualEncounters += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def description(d: String): O = {
|
||||||
|
_description = d
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def hidden(): O = {
|
||||||
|
_hidden = true
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def minCount(count: Int): O = {
|
||||||
|
_minEncounters = count
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def maxCount(count: Int): O = {
|
||||||
|
_maxEncounters = count
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def required(): O = minCount(1)
|
||||||
|
|
||||||
|
def repeatable(): O = maxCount(Int.MaxValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Fluff[T](val text: Seq[String]) extends CliOption[T, Fluff[T]] {
|
||||||
|
this.repeatable()
|
||||||
|
|
||||||
|
override def toStrings(firstTab: Int): List[String] = text.toList
|
||||||
|
|
||||||
|
override def length = 0
|
||||||
|
|
||||||
|
override val _shortName = ""
|
||||||
|
|
||||||
|
override def names = Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoMoreOptions[T](val names: Seq[String]) extends CliOption[T, NoMoreOptions[T]] {
|
||||||
|
this.repeatable()
|
||||||
|
|
||||||
|
override def length = 1
|
||||||
|
|
||||||
|
override val _shortName = names.head
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnknownParamOption[T] extends CliOption[T, UnknownParamOption[T]] {
|
||||||
|
this._hidden = true
|
||||||
|
|
||||||
|
override def length = 0
|
||||||
|
|
||||||
|
val names: Seq[String] = Nil
|
||||||
|
private var _action: ((String, T) => T) = (_, x) => x
|
||||||
|
|
||||||
|
def action(a: ((String, T) => T)): UnknownParamOption[T] = {
|
||||||
|
_action = a
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def encounter(value: String, t: T): T = {
|
||||||
|
encounter()
|
||||||
|
_action(value, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
override private[cli] val _shortName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlagOption[T](val names: Seq[String]) extends CliOption[T, FlagOption[T]] {
|
||||||
|
override def length = 1
|
||||||
|
|
||||||
|
private var _action: (T => T) = x => x
|
||||||
|
|
||||||
|
def action(a: (T => T)): FlagOption[T] = {
|
||||||
|
_action = a
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def encounter(t: T): T = {
|
||||||
|
encounter()
|
||||||
|
_action(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val _shortName = names.head
|
||||||
|
}
|
||||||
|
|
||||||
|
class BooleanOption[T](val trueName: String, val falseName: String) extends CliOption[T, BooleanOption[T]] {
|
||||||
|
override def length = 1
|
||||||
|
|
||||||
|
private var _action: ((T,Boolean) => T) = (x,_) => x
|
||||||
|
|
||||||
|
def action(a: ((T,Boolean) => T)): BooleanOption[T] = {
|
||||||
|
_action = a
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def encounter(asName: String, t: T): T = {
|
||||||
|
encounter()
|
||||||
|
if (asName == trueName) {
|
||||||
|
return _action(t, true)
|
||||||
|
}
|
||||||
|
if (asName == falseName) {
|
||||||
|
return _action(t, false)
|
||||||
|
}
|
||||||
|
t
|
||||||
|
}
|
||||||
|
|
||||||
|
override val _shortName = names.head
|
||||||
|
|
||||||
|
override protected def firstLine: String = trueName + " | " + falseName
|
||||||
|
|
||||||
|
override def names = Seq(trueName, falseName)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParamOption[T](val names: Seq[String]) extends CliOption[T, ParamOption[T]] {
|
||||||
|
|
||||||
|
override protected def firstLine: String = names.mkString(" | ") + " " + _paramPlaceholder
|
||||||
|
|
||||||
|
override def length = 2
|
||||||
|
|
||||||
|
private var _action: ((String, T) => T) = (_, x) => x
|
||||||
|
private var _paramPlaceholder: String = "<x>"
|
||||||
|
|
||||||
|
def placeholder(p: String): ParamOption[T] = {
|
||||||
|
_paramPlaceholder = p
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def action(a: ((String, T) => T)): ParamOption[T] = {
|
||||||
|
_action = a
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def encounter(value: String, t: T): T = {
|
||||||
|
encounter()
|
||||||
|
_action(value, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val _shortName = names.head
|
||||||
|
}
|
81
src/main/scala/millfork/cli/CliParser.scala
Normal file
81
src/main/scala/millfork/cli/CliParser.scala
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package millfork.cli
|
||||||
|
|
||||||
|
import fastparse.core.Parsed.Failure
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
class CliParser[T] {
|
||||||
|
|
||||||
|
private val options = mutable.ArrayBuffer[CliOption[T, _]]()
|
||||||
|
private val mapFlags = mutable.Map[String, CliOption[T, _]]()
|
||||||
|
private val mapOptions = mutable.Map[String, CliOption[T, _]]()
|
||||||
|
private val _default = new UnknownParamOption[T]().action((p, _) => throw new IllegalArgumentException(s"Unknown option $p"))
|
||||||
|
private var _status: Option[CliStatus.Value] = None
|
||||||
|
options += _default
|
||||||
|
|
||||||
|
private def add[O <: CliOption[T, _]](o: O) = {
|
||||||
|
options += o
|
||||||
|
o.length match {
|
||||||
|
case 1 =>
|
||||||
|
o.names.foreach { n => mapFlags(n) = o }
|
||||||
|
case 2 =>
|
||||||
|
o.names.foreach { n => mapOptions(n) = o }
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
o
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse(context: T, args: List[String]): (CliStatus.Value, T) = {
|
||||||
|
val t = parseInner(context, args)
|
||||||
|
_status.getOrElse(if (options.forall(_.validate())) CliStatus.Ok else CliStatus.Failed) -> t
|
||||||
|
}
|
||||||
|
|
||||||
|
def assumeStatus(s: CliStatus.Value): Unit = {
|
||||||
|
_status = Some(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def parseInner(context: T, args: List[String]): T = {
|
||||||
|
args match {
|
||||||
|
case k :: v :: xs if mapOptions.contains(k) =>
|
||||||
|
mapOptions(k) match {
|
||||||
|
case p: ParamOption[T] => parseInner(p.encounter(v, context), xs)
|
||||||
|
case _ => ???
|
||||||
|
}
|
||||||
|
case k :: xs if mapFlags.contains(k) =>
|
||||||
|
mapFlags(k) match {
|
||||||
|
case p: FlagOption[T] =>
|
||||||
|
parseInner(p.encounter(context), xs)
|
||||||
|
case p: BooleanOption[T] =>
|
||||||
|
parseInner(p.encounter(k, context), xs)
|
||||||
|
case p: NoMoreOptions[T] =>
|
||||||
|
p.encounter()
|
||||||
|
xs.foldLeft(context)((t, x) => _default.encounter(x, t))
|
||||||
|
case _ => ???
|
||||||
|
}
|
||||||
|
case x :: xs =>
|
||||||
|
parseInner(_default.encounter(x, context), xs)
|
||||||
|
case Nil => context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fluff(text: String*): Unit = add(new Fluff[T](text))
|
||||||
|
|
||||||
|
def flag(names: String*): FlagOption[T] = add(new FlagOption[T](names))
|
||||||
|
|
||||||
|
def boolean(trueName: String, falseName: String): BooleanOption[T] = add(new BooleanOption[T](trueName, falseName))
|
||||||
|
|
||||||
|
def endOfFlags(names: String*): NoMoreOptions[T] = add(new NoMoreOptions[T](names))
|
||||||
|
|
||||||
|
def default: UnknownParamOption[T] = _default
|
||||||
|
|
||||||
|
def printHelp(firstTab: Int): List[String] = {
|
||||||
|
options.filterNot(_._hidden).toList.flatMap(_.toStrings(firstTab))
|
||||||
|
}
|
||||||
|
|
||||||
|
def parameter(names: String*): ParamOption[T] = add(new ParamOption[T](names))
|
||||||
|
|
||||||
|
}
|
8
src/main/scala/millfork/cli/CliStatus.scala
Normal file
8
src/main/scala/millfork/cli/CliStatus.scala
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package millfork.cli
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object CliStatus extends Enumeration {
|
||||||
|
val Ok, Failed, Quit = Value
|
||||||
|
}
|
832
src/main/scala/millfork/compiler/BuiltIns.scala
Normal file
832
src/main/scala/millfork/compiler/BuiltIns.scala
Normal file
|
@ -0,0 +1,832 @@
|
||||||
|
package millfork.compiler
|
||||||
|
|
||||||
|
import millfork.{CompilationFlag, CompilationOptions}
|
||||||
|
import millfork.assembly._
|
||||||
|
import millfork.env._
|
||||||
|
import millfork.node._
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
import scala.collection.mutable.ListBuffer
|
||||||
|
import scala.reflect.macros.blackbox
|
||||||
|
|
||||||
|
|
||||||
|
object ComparisonType extends Enumeration {
|
||||||
|
val Equal, NotEqual,
|
||||||
|
LessUnsigned, LessSigned,
|
||||||
|
GreaterUnsigned, GreaterSigned,
|
||||||
|
LessOrEqualUnsigned, LessOrEqualSigned,
|
||||||
|
GreaterOrEqualUnsigned, GreaterOrEqualSigned = Value
|
||||||
|
|
||||||
|
def flip(x: ComparisonType.Value): ComparisonType.Value = x match {
|
||||||
|
case LessUnsigned => GreaterUnsigned
|
||||||
|
case GreaterUnsigned => LessUnsigned
|
||||||
|
case LessOrEqualUnsigned => GreaterOrEqualUnsigned
|
||||||
|
case GreaterOrEqualUnsigned => LessOrEqualUnsigned
|
||||||
|
case LessSigned => GreaterSigned
|
||||||
|
case GreaterSigned => LessSigned
|
||||||
|
case LessOrEqualSigned => GreaterOrEqualSigned
|
||||||
|
case GreaterOrEqualSigned => LessOrEqualSigned
|
||||||
|
case _ => x
|
||||||
|
}
|
||||||
|
|
||||||
|
def negate(x: ComparisonType.Value): ComparisonType.Value = x match {
|
||||||
|
case LessUnsigned => GreaterOrEqualUnsigned
|
||||||
|
case GreaterUnsigned => LessOrEqualUnsigned
|
||||||
|
case LessOrEqualUnsigned => GreaterUnsigned
|
||||||
|
case GreaterOrEqualUnsigned => LessUnsigned
|
||||||
|
case LessSigned => GreaterOrEqualSigned
|
||||||
|
case GreaterSigned => LessOrEqualSigned
|
||||||
|
case LessOrEqualSigned => GreaterSigned
|
||||||
|
case GreaterOrEqualSigned => LessSigned
|
||||||
|
case Equal => NotEqual
|
||||||
|
case NotEqual => Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object BuiltIns {
|
||||||
|
|
||||||
|
object IndexChoice extends Enumeration {
|
||||||
|
val RequireX, PreferX, PreferY = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
def wrapInSedCldIfNeeded(decimal: Boolean, code: List[AssemblyLine]): List[AssemblyLine] = {
|
||||||
|
if (decimal) {
|
||||||
|
AssemblyLine.implied(SED) :: (code :+ AssemblyLine.implied(CLD))
|
||||||
|
} else {
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def staTo(op: Opcode.Value, l: List[AssemblyLine]): List[AssemblyLine] = l.map(x => if (x.opcode == STA) x.copy(opcode = op) else x)
|
||||||
|
|
||||||
|
def ldTo(op: Opcode.Value, l: List[AssemblyLine]): List[AssemblyLine] = l.map(x => if (x.opcode == LDA || x.opcode == LDX || x.opcode == LDY) x.copy(opcode = op) else x)
|
||||||
|
|
||||||
|
def simpleOperation(opcode: Opcode.Value, ctx: CompilationContext, source: Expression, indexChoice: IndexChoice.Value, preserveA: Boolean, commutative: Boolean): List[AssemblyLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val parts: (List[AssemblyLine], List[AssemblyLine]) = env.eval(source).fold {
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
source match {
|
||||||
|
case VariableExpression(name) =>
|
||||||
|
val v = env.get[Variable](name)
|
||||||
|
if (v.typ.size > 1) {
|
||||||
|
ErrorReporting.error(s"Variable `$name` is too big for a built-in operation", source.position)
|
||||||
|
return Nil
|
||||||
|
}
|
||||||
|
Nil -> AssemblyLine.variable(ctx, opcode, v)
|
||||||
|
case IndexedExpression(arrayName, index) =>
|
||||||
|
indexChoice match {
|
||||||
|
case IndexChoice.RequireX | IndexChoice.PreferX =>
|
||||||
|
val array = env.getArrayOrPointer(arrayName)
|
||||||
|
val calculateIndex = MlCompiler.compile(ctx, index, Some(b -> RegisterVariable(Register.X, b)), NoBranching)
|
||||||
|
val baseAddress = array match {
|
||||||
|
case c: ConstantThing => c.value
|
||||||
|
case a: MlArray => a.toAddress
|
||||||
|
}
|
||||||
|
calculateIndex -> List(AssemblyLine.absoluteX(opcode, baseAddress))
|
||||||
|
case IndexChoice.PreferY =>
|
||||||
|
val array = env.getArrayOrPointer(arrayName)
|
||||||
|
val calculateIndex = MlCompiler.compile(ctx, index, Some(b -> RegisterVariable(Register.Y, b)), NoBranching)
|
||||||
|
val baseAddress = array match {
|
||||||
|
case c: ConstantThing => c.value
|
||||||
|
case a: MlArray => a.toAddress
|
||||||
|
}
|
||||||
|
calculateIndex -> List(AssemblyLine.absoluteY(opcode, baseAddress))
|
||||||
|
}
|
||||||
|
case f: FunctionCallExpression if commutative =>
|
||||||
|
// TODO: is it ok?
|
||||||
|
return List(AssemblyLine.implied(PHA)) ++ MlCompiler.compile(ctx.addStack(1), f, Some(b -> RegisterVariable(Register.A, b)), NoBranching) ++ List(
|
||||||
|
AssemblyLine.implied(TSX),
|
||||||
|
AssemblyLine.absoluteX(opcode, 0x101),
|
||||||
|
AssemblyLine.implied(INX),
|
||||||
|
AssemblyLine.implied(TXS))
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Right-hand-side expression is too complex", source.position)
|
||||||
|
return Nil
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
const =>
|
||||||
|
if (const.requiredSize > 1) {
|
||||||
|
ErrorReporting.error("Constant too big for a built-in operation", source.position)
|
||||||
|
}
|
||||||
|
Nil -> List(AssemblyLine.immediate(opcode, const))
|
||||||
|
}
|
||||||
|
val preparations = parts._1
|
||||||
|
val finalRead = parts._2
|
||||||
|
if (preserveA && AssemblyLine.treatment(preparations, State.A) != Treatment.Unchanged) {
|
||||||
|
AssemblyLine.implied(PHA) :: (preparations ++ (AssemblyLine.implied(PLA) :: finalRead))
|
||||||
|
} else {
|
||||||
|
preparations ++ finalRead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def insertBeforeLast(item: AssemblyLine, list: List[AssemblyLine]): List[AssemblyLine] = list match {
|
||||||
|
case Nil => Nil
|
||||||
|
case last :: dex :: txs :: Nil if dex.opcode == DEX && txs.opcode == TXS => item :: last :: dex :: txs :: Nil
|
||||||
|
case last :: inx :: txs :: Nil if inx.opcode == INX && txs.opcode == TXS => item :: last :: inx :: txs :: Nil
|
||||||
|
case last :: Nil => item :: last :: Nil
|
||||||
|
case first :: rest => first :: insertBeforeLast(item, rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileAddition(ctx: CompilationContext, params: List[(Boolean, Expression)], decimal: Boolean): List[AssemblyLine] = {
|
||||||
|
if (decimal && !ctx.options.flag(CompilationFlag.DecimalMode)) {
|
||||||
|
ErrorReporting.warn("Unsupported decimal operation", ctx.options, params.head._2.position)
|
||||||
|
}
|
||||||
|
// if (params.isEmpty) {
|
||||||
|
// return Nil
|
||||||
|
// }
|
||||||
|
val env = ctx.env
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
val sortedParams = params.sortBy { case (subtract, expr) =>
|
||||||
|
val constPart = env.eval(expr) match {
|
||||||
|
case Some(NumericConstant(_, _)) => "Z"
|
||||||
|
case Some(_) => "Y"
|
||||||
|
case None => expr match {
|
||||||
|
case VariableExpression(_) => "V"
|
||||||
|
case IndexedExpression(_, LiteralExpression(_, _)) => "K"
|
||||||
|
case IndexedExpression(_, VariableExpression(_)) => "J"
|
||||||
|
case IndexedExpression(_, _) => "I"
|
||||||
|
case _ => "A"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val subtractPart = if (subtract) "X" else "P"
|
||||||
|
constPart + subtractPart
|
||||||
|
}
|
||||||
|
// TODO: merge constants
|
||||||
|
val normalizedParams = sortedParams
|
||||||
|
|
||||||
|
val h = normalizedParams.head
|
||||||
|
val firstParamCompiled = MlCompiler.compile(ctx, h._2, Some(b -> RegisterVariable(Register.A, b)), NoBranching)
|
||||||
|
val firstParamSignCompiled = if (h._1) {
|
||||||
|
List(AssemblyLine.immediate(EOR, 0xff), AssemblyLine.implied(SEC), AssemblyLine.immediate(ADC, 0))
|
||||||
|
} else {
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
val remainingParamsCompiled = normalizedParams.tail.flatMap { p =>
|
||||||
|
if (p._1) {
|
||||||
|
insertBeforeLast(AssemblyLine.implied(SEC), simpleOperation(SBC, ctx, p._2, IndexChoice.PreferY, preserveA = true, commutative = false))
|
||||||
|
} else {
|
||||||
|
insertBeforeLast(AssemblyLine.implied(CLC), simpleOperation(ADC, ctx, p._2, IndexChoice.PreferY, preserveA = true, commutative = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapInSedCldIfNeeded(decimal, firstParamCompiled ++ firstParamSignCompiled ++ remainingParamsCompiled)
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileBitOps(opcode: Opcode.Value, ctx: CompilationContext, params: List[Expression]): List[AssemblyLine] = {
|
||||||
|
val b = ctx.env.get[Type]("byte")
|
||||||
|
|
||||||
|
val sortedParams = params.sortBy { expr =>
|
||||||
|
ctx.env.eval(expr) match {
|
||||||
|
case Some(NumericConstant(_, _)) => "Z"
|
||||||
|
case Some(_) => "Y"
|
||||||
|
case None => expr match {
|
||||||
|
case VariableExpression(_) => "V"
|
||||||
|
case IndexedExpression(_, LiteralExpression(_, _)) => "K"
|
||||||
|
case IndexedExpression(_, VariableExpression(_)) => "J"
|
||||||
|
case IndexedExpression(_, _) => "I"
|
||||||
|
case _ => "A"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val h = sortedParams.head
|
||||||
|
val firstParamCompiled = MlCompiler.compile(ctx, h, Some(b -> RegisterVariable(Register.A, b)), NoBranching)
|
||||||
|
|
||||||
|
val remainingParamsCompiled = sortedParams.tail.flatMap { p =>
|
||||||
|
simpleOperation(opcode, ctx, p, IndexChoice.PreferY, preserveA = true, commutative = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
firstParamCompiled ++ remainingParamsCompiled
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileShiftOps(opcode: Opcode.Value, ctx: CompilationContext, l: Expression, r: Expression): List[AssemblyLine] = {
|
||||||
|
val b = ctx.env.get[Type]("byte")
|
||||||
|
val firstParamCompiled = MlCompiler.compile(ctx, l, Some(b -> RegisterVariable(Register.A, b)), NoBranching)
|
||||||
|
ctx.env.eval(r) match {
|
||||||
|
case Some(NumericConstant(0, _)) =>
|
||||||
|
Nil
|
||||||
|
case Some(NumericConstant(v, _)) if v > 0 =>
|
||||||
|
firstParamCompiled ++ List.fill(v.toInt)(AssemblyLine.implied(opcode))
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Cannot shift by a non-constant amount")
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileNonetOps(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression): List[AssemblyLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
val (ldaHi, ldaLo) = lhs match {
|
||||||
|
case v: VariableExpression =>
|
||||||
|
val variable = env.get[Variable](v.name)
|
||||||
|
AssemblyLine.variable(ctx, LDA, variable, 1) -> AssemblyLine.variable(ctx, LDA, variable, 0)
|
||||||
|
case SeparateBytesExpression(h: VariableExpression, l: VariableExpression) =>
|
||||||
|
AssemblyLine.variable(ctx, LDA, env.get[Variable](h.name), 0) -> AssemblyLine.variable(ctx, LDA, env.get[Variable](l.name), 0)
|
||||||
|
case _ =>
|
||||||
|
???
|
||||||
|
}
|
||||||
|
env.eval(rhs) match {
|
||||||
|
case Some(NumericConstant(0, _)) =>
|
||||||
|
Nil
|
||||||
|
case Some(NumericConstant(shift, _)) if shift > 0 =>
|
||||||
|
if (ctx.options.flag(CompilationFlag.RorWarning))
|
||||||
|
ErrorReporting.warn("ROR instruction generated", ctx.options, lhs.position)
|
||||||
|
ldaHi ++ List(AssemblyLine.implied(ROR)) ++ ldaLo ++ List(AssemblyLine.implied(ROR)) ++ List.fill(shift.toInt - 1)(AssemblyLine.implied(LSR))
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Non-constant shift amount", rhs.position) // TODO
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileInPlaceByteShiftOps(opcode: Opcode.Value, ctx: CompilationContext, lhs: LhsExpression, rhs: Expression): List[AssemblyLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
val firstParamCompiled = MlCompiler.compile(ctx, lhs, Some(b -> RegisterVariable(Register.A, b)), NoBranching)
|
||||||
|
env.eval(rhs) match {
|
||||||
|
case Some(NumericConstant(0, _)) =>
|
||||||
|
Nil
|
||||||
|
case Some(NumericConstant(v, _)) if v > 0 =>
|
||||||
|
val result = simpleOperation(opcode, ctx, lhs, IndexChoice.RequireX, preserveA = true, commutative = false)
|
||||||
|
result ++ List.fill(v.toInt - 1)(result.last)
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Non-constant shift amount", rhs.position) // TODO
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileInPlaceWordOrLongShiftOps(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, aslRatherThanLsr: Boolean): List[AssemblyLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
val targetBytes = lhs match {
|
||||||
|
case v: VariableExpression =>
|
||||||
|
val variable = env.get[Variable](v.name)
|
||||||
|
List.tabulate(variable.typ.size) { i => AssemblyLine.variable(ctx, STA, variable, i) }
|
||||||
|
case SeparateBytesExpression(h: VariableExpression, l: VariableExpression) =>
|
||||||
|
List(
|
||||||
|
AssemblyLine.variable(ctx, STA, env.get[Variable](l.name)),
|
||||||
|
AssemblyLine.variable(ctx, STA, env.get[Variable](h.name)))
|
||||||
|
}
|
||||||
|
val lo = targetBytes.head
|
||||||
|
val hi = targetBytes.last
|
||||||
|
env.eval(rhs) match {
|
||||||
|
case Some(NumericConstant(0, _)) =>
|
||||||
|
Nil
|
||||||
|
case Some(NumericConstant(shift, _)) if shift > 0 =>
|
||||||
|
List.fill(shift.toInt)(if (aslRatherThanLsr) {
|
||||||
|
staTo(ASL, lo) ++ targetBytes.tail.flatMap { b => staTo(ROL, b) }
|
||||||
|
} else {
|
||||||
|
if (ctx.options.flag(CompilationFlag.RorWarning))
|
||||||
|
ErrorReporting.warn("ROR instruction generated", ctx.options, lhs.position)
|
||||||
|
staTo(LSR, hi) ++ targetBytes.reverse.tail.flatMap { b => staTo(ROR, b) }
|
||||||
|
}).flatten
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Non-constant shift amount", rhs.position) // TODO
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileByteComparison(ctx: CompilationContext, compType: ComparisonType.Value, lhs: Expression, rhs: Expression, branches: BranchSpec): List[AssemblyLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
val firstParamCompiled = MlCompiler.compile(ctx, lhs, Some(b -> RegisterVariable(Register.A, b)), NoBranching)
|
||||||
|
env.eval(rhs) match {
|
||||||
|
case Some(NumericConstant(0, _)) =>
|
||||||
|
compType match {
|
||||||
|
case ComparisonType.LessUnsigned =>
|
||||||
|
ErrorReporting.warn("Unsigned < 0 is always false", ctx.options, lhs.position)
|
||||||
|
case ComparisonType.LessOrEqualUnsigned =>
|
||||||
|
if (ctx.options.flag(CompilationFlag.ExtraComparisonWarnings))
|
||||||
|
ErrorReporting.warn("Unsigned <= 0 means the same as unsigned == 0", ctx.options, lhs.position)
|
||||||
|
case ComparisonType.GreaterUnsigned =>
|
||||||
|
if (ctx.options.flag(CompilationFlag.ExtraComparisonWarnings))
|
||||||
|
ErrorReporting.warn("Unsigned > 0 means the same as unsigned != 0", ctx.options, lhs.position)
|
||||||
|
case ComparisonType.GreaterOrEqualUnsigned =>
|
||||||
|
ErrorReporting.warn("Unsigned >= 0 is always true", ctx.options, lhs.position)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
case Some(NumericConstant(1, _)) =>
|
||||||
|
if (ctx.options.flag(CompilationFlag.ExtraComparisonWarnings)) {
|
||||||
|
compType match {
|
||||||
|
case ComparisonType.LessUnsigned =>
|
||||||
|
ErrorReporting.warn("Unsigned < 1 means the same as unsigned == 0", ctx.options, lhs.position)
|
||||||
|
case ComparisonType.GreaterOrEqualUnsigned =>
|
||||||
|
ErrorReporting.warn("Unsigned >= 1 means the same as unsigned != 0", ctx.options, lhs.position)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
val secondParamCompiledUnoptimized = simpleOperation(CMP, ctx, rhs, IndexChoice.PreferY, preserveA = true, commutative = false)
|
||||||
|
val secondParamCompiled = compType match {
|
||||||
|
case ComparisonType.Equal | ComparisonType.NotEqual | ComparisonType.LessSigned | ComparisonType.GreaterOrEqualSigned =>
|
||||||
|
secondParamCompiledUnoptimized match {
|
||||||
|
case List(AssemblyLine(CMP, Immediate, NumericConstant(0, _), true)) =>
|
||||||
|
if (OpcodeClasses.ChangesAAlways(firstParamCompiled.last.opcode)) {
|
||||||
|
Nil
|
||||||
|
} else {
|
||||||
|
secondParamCompiledUnoptimized
|
||||||
|
}
|
||||||
|
case _ => secondParamCompiledUnoptimized
|
||||||
|
}
|
||||||
|
case _ => secondParamCompiledUnoptimized
|
||||||
|
}
|
||||||
|
val (effectiveComparisonType, label) = branches match {
|
||||||
|
case NoBranching => return Nil
|
||||||
|
case BranchIfTrue(l) => compType -> l
|
||||||
|
case BranchIfFalse(l) => ComparisonType.negate(compType) -> l
|
||||||
|
}
|
||||||
|
val branchingCompiled = effectiveComparisonType match {
|
||||||
|
case ComparisonType.Equal =>
|
||||||
|
List(AssemblyLine.relative(BEQ, Label(label)))
|
||||||
|
case ComparisonType.NotEqual =>
|
||||||
|
List(AssemblyLine.relative(BNE, Label(label)))
|
||||||
|
|
||||||
|
case ComparisonType.LessUnsigned =>
|
||||||
|
List(AssemblyLine.relative(BCC, Label(label)))
|
||||||
|
case ComparisonType.GreaterOrEqualUnsigned =>
|
||||||
|
List(AssemblyLine.relative(BCS, Label(label)))
|
||||||
|
case ComparisonType.LessOrEqualUnsigned =>
|
||||||
|
List(AssemblyLine.relative(BCC, Label(label)), AssemblyLine.relative(BEQ, Label(label)))
|
||||||
|
case ComparisonType.GreaterUnsigned =>
|
||||||
|
val x = MlCompiler.nextLabel("co")
|
||||||
|
List(
|
||||||
|
AssemblyLine.relative(BEQ, x),
|
||||||
|
AssemblyLine.relative(BCS, Label(label)),
|
||||||
|
AssemblyLine.label(x))
|
||||||
|
|
||||||
|
case ComparisonType.LessSigned =>
|
||||||
|
List(AssemblyLine.relative(BMI, Label(label)))
|
||||||
|
case ComparisonType.GreaterOrEqualSigned =>
|
||||||
|
List(AssemblyLine.relative(BPL, Label(label)))
|
||||||
|
case ComparisonType.LessOrEqualSigned =>
|
||||||
|
List(AssemblyLine.relative(BMI, Label(label)), AssemblyLine.relative(BEQ, Label(label)))
|
||||||
|
case ComparisonType.GreaterSigned =>
|
||||||
|
val x = MlCompiler.nextLabel("co")
|
||||||
|
List(
|
||||||
|
AssemblyLine.relative(BEQ, x),
|
||||||
|
AssemblyLine.relative(BPL, Label(label)),
|
||||||
|
AssemblyLine.label(x))
|
||||||
|
}
|
||||||
|
firstParamCompiled ++ secondParamCompiled ++ branchingCompiled
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileWordComparison(ctx: CompilationContext, compType: ComparisonType.Value, lhs: Expression, rhs: Expression, branches: BranchSpec): List[AssemblyLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
// TODO: comparing stack variables
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
val w = env.get[Type]("word")
|
||||||
|
|
||||||
|
val (effectiveComparisonType, x) = branches match {
|
||||||
|
case NoBranching => return Nil
|
||||||
|
case BranchIfTrue(label) => compType -> label
|
||||||
|
case BranchIfFalse(label) => ComparisonType.negate(compType) -> label
|
||||||
|
}
|
||||||
|
val (lh, ll, rh, rl, ram) = (lhs, env.eval(lhs), rhs, env.eval(rhs)) match {
|
||||||
|
case (_, Some(NumericConstant(lc, _)), _, Some(NumericConstant(rc, _))) =>
|
||||||
|
return if (effectiveComparisonType match {
|
||||||
|
// TODO: those masks are probably wrong
|
||||||
|
case ComparisonType.Equal =>
|
||||||
|
(lc & 0xffff) == (rc & 0xffff) // ??
|
||||||
|
case ComparisonType.NotEqual =>
|
||||||
|
(lc & 0xffff) != (rc & 0xffff) // ??
|
||||||
|
|
||||||
|
case ComparisonType.LessOrEqualUnsigned =>
|
||||||
|
(lc & 0xffff) <= (rc & 0xffff)
|
||||||
|
case ComparisonType.GreaterOrEqualUnsigned =>
|
||||||
|
(lc & 0xffff) >= (rc & 0xffff)
|
||||||
|
case ComparisonType.GreaterUnsigned =>
|
||||||
|
(lc & 0xffff) > (rc & 0xffff)
|
||||||
|
case ComparisonType.LessUnsigned =>
|
||||||
|
(lc & 0xffff) < (rc & 0xffff)
|
||||||
|
|
||||||
|
case ComparisonType.LessOrEqualSigned =>
|
||||||
|
lc.toShort <= rc.toShort
|
||||||
|
case ComparisonType.GreaterOrEqualSigned =>
|
||||||
|
lc.toShort >= rc.toShort
|
||||||
|
case ComparisonType.GreaterSigned =>
|
||||||
|
lc.toShort > rc.toShort
|
||||||
|
case ComparisonType.LessSigned =>
|
||||||
|
lc.toShort < rc.toShort
|
||||||
|
}) List(AssemblyLine.absolute(JMP, Label(x))) else Nil
|
||||||
|
case (_, Some(lc), _, Some(rc)) =>
|
||||||
|
// TODO: comparing late-bound constants
|
||||||
|
???
|
||||||
|
case (_, Some(lc), rv: VariableInMemory, None) =>
|
||||||
|
return compileWordComparison(ctx, ComparisonType.flip(compType), rhs, lhs, branches)
|
||||||
|
case (v: VariableExpression, None, _, Some(rc)) =>
|
||||||
|
// TODO: stack variables
|
||||||
|
(env.get[VariableInMemory](v.name + ".hi").toAddress,
|
||||||
|
env.get[VariableInMemory](v.name + ".lo").toAddress,
|
||||||
|
rc.hiByte,
|
||||||
|
rc.loByte,
|
||||||
|
Immediate)
|
||||||
|
case (lv: VariableExpression, None, rv: VariableExpression, None) =>
|
||||||
|
// TODO: stack variables
|
||||||
|
(env.get[VariableInMemory](lv.name + ".hi").toAddress,
|
||||||
|
env.get[VariableInMemory](lv.name + ".lo").toAddress,
|
||||||
|
env.get[VariableInMemory](rv.name + ".hi").toAddress,
|
||||||
|
env.get[VariableInMemory](rv.name + ".lo").toAddress, Absolute)
|
||||||
|
}
|
||||||
|
effectiveComparisonType match {
|
||||||
|
case ComparisonType.Equal =>
|
||||||
|
val innerLabel = MlCompiler.nextLabel("cp")
|
||||||
|
List(AssemblyLine.absolute(LDA, ll),
|
||||||
|
AssemblyLine(CMP, ram, rl),
|
||||||
|
AssemblyLine.relative(BNE, innerLabel),
|
||||||
|
AssemblyLine.absolute(LDA, lh),
|
||||||
|
AssemblyLine(CMP, ram, rh),
|
||||||
|
AssemblyLine.relative(BEQ, Label(x)),
|
||||||
|
AssemblyLine.label(innerLabel))
|
||||||
|
|
||||||
|
case ComparisonType.NotEqual =>
|
||||||
|
List(AssemblyLine.absolute(LDA, ll),
|
||||||
|
AssemblyLine(CMP, ram, rl),
|
||||||
|
AssemblyLine.relative(BNE, Label(x)),
|
||||||
|
AssemblyLine.absolute(LDA, lh),
|
||||||
|
AssemblyLine(CMP, ram, rh),
|
||||||
|
AssemblyLine.relative(BNE, Label(x)))
|
||||||
|
|
||||||
|
case ComparisonType.LessUnsigned =>
|
||||||
|
val innerLabel = MlCompiler.nextLabel("cp")
|
||||||
|
List(AssemblyLine.absolute(LDA, lh),
|
||||||
|
AssemblyLine(CMP, ram, rh),
|
||||||
|
AssemblyLine.relative(BCC, Label(x)),
|
||||||
|
AssemblyLine.relative(BNE, innerLabel),
|
||||||
|
AssemblyLine.absolute(LDA, ll),
|
||||||
|
AssemblyLine(CMP, ram, rl),
|
||||||
|
AssemblyLine.relative(BCC, Label(x)),
|
||||||
|
AssemblyLine.label(innerLabel))
|
||||||
|
|
||||||
|
case ComparisonType.LessOrEqualUnsigned =>
|
||||||
|
val innerLabel = MlCompiler.nextLabel("cp")
|
||||||
|
List(AssemblyLine(LDA, ram, rh),
|
||||||
|
AssemblyLine.absolute(CMP, lh),
|
||||||
|
AssemblyLine.relative(BCC, innerLabel),
|
||||||
|
AssemblyLine.relative(BNE, x),
|
||||||
|
AssemblyLine(LDA, ram, rl),
|
||||||
|
AssemblyLine.absolute(CMP, ll),
|
||||||
|
AssemblyLine.relative(BCS, x),
|
||||||
|
AssemblyLine.label(innerLabel))
|
||||||
|
|
||||||
|
case ComparisonType.GreaterUnsigned =>
|
||||||
|
val innerLabel = MlCompiler.nextLabel("cp")
|
||||||
|
List(AssemblyLine(LDA, ram, rh),
|
||||||
|
AssemblyLine.absolute(CMP, lh),
|
||||||
|
AssemblyLine.relative(BCC, Label(x)),
|
||||||
|
AssemblyLine.relative(BNE, innerLabel),
|
||||||
|
AssemblyLine(LDA, ram, rl),
|
||||||
|
AssemblyLine.absolute(CMP, ll),
|
||||||
|
AssemblyLine.relative(BCC, Label(x)),
|
||||||
|
AssemblyLine.label(innerLabel))
|
||||||
|
|
||||||
|
case ComparisonType.GreaterOrEqualUnsigned =>
|
||||||
|
val innerLabel = MlCompiler.nextLabel("cp")
|
||||||
|
List(AssemblyLine.absolute(LDA, lh),
|
||||||
|
AssemblyLine(CMP, ram, rh),
|
||||||
|
AssemblyLine.relative(BCC, innerLabel),
|
||||||
|
AssemblyLine.relative(BNE, x),
|
||||||
|
AssemblyLine.absolute(LDA, ll),
|
||||||
|
AssemblyLine(CMP, ram, rl),
|
||||||
|
AssemblyLine.relative(BCS, x),
|
||||||
|
AssemblyLine.label(innerLabel))
|
||||||
|
|
||||||
|
case _ => ???
|
||||||
|
// TODO: signed word comparisons
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileInPlaceByteMultiplication(ctx: CompilationContext, v: LhsExpression, addend: Expression): List[AssemblyLine] = {
|
||||||
|
val b = ctx.env.get[Type]("byte")
|
||||||
|
ctx.env.eval(addend) match {
|
||||||
|
case Some(NumericConstant(0, _)) =>
|
||||||
|
AssemblyLine.immediate(LDA, 0) :: MlCompiler.compileByteStorage(ctx, Register.A, v)
|
||||||
|
case Some(NumericConstant(1, _)) =>
|
||||||
|
Nil
|
||||||
|
case Some(NumericConstant(x, _)) =>
|
||||||
|
compileByteMultiplication(ctx, v, x.toInt) ++ MlCompiler.compileByteStorage(ctx, Register.A, v)
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Multiplying by not a constant not supported", v.position)
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileByteMultiplication(ctx: CompilationContext, v: Expression, c: Int): List[AssemblyLine] = {
|
||||||
|
val result = ListBuffer[AssemblyLine]()
|
||||||
|
// TODO: optimise
|
||||||
|
val addingCode = simpleOperation(ADC, ctx, v, IndexChoice.PreferY, preserveA = false, commutative = false)
|
||||||
|
val adc = addingCode.last
|
||||||
|
val indexing = addingCode.init
|
||||||
|
result ++= indexing
|
||||||
|
result += AssemblyLine.immediate(LDA, 0)
|
||||||
|
val mult = c & 0xff
|
||||||
|
var mask = 128
|
||||||
|
var empty = true
|
||||||
|
while (mask > 0) {
|
||||||
|
if (!empty) {
|
||||||
|
result += AssemblyLine.implied(ASL)
|
||||||
|
}
|
||||||
|
if ((mult & mask) != 0) {
|
||||||
|
result ++= List(AssemblyLine.implied(CLC), adc)
|
||||||
|
empty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
mask >>>= 1
|
||||||
|
}
|
||||||
|
result.toList
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileByteMultiplication(ctx: CompilationContext, params: List[Expression]): List[AssemblyLine] = {
|
||||||
|
val (constants, variables) = params.map(p => p -> ctx.env.eval(p)).partition(_._2.exists(_.isInstanceOf[NumericConstant]))
|
||||||
|
val constant = constants.map(_._2.get.asInstanceOf[NumericConstant].value).foldLeft(1L)(_ * _).toInt
|
||||||
|
variables.length match {
|
||||||
|
case 0 => List(AssemblyLine.immediate(LDA, constant & 0xff))
|
||||||
|
case 1 =>compileByteMultiplication(ctx, variables.head._1, constant)
|
||||||
|
case 2 =>
|
||||||
|
ErrorReporting.error("Multiplying by not a constant not supported", params.head.position)
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileInPlaceByteAddition(ctx: CompilationContext, v: LhsExpression, addend: Expression, subtract: Boolean, decimal: Boolean): List[AssemblyLine] = {
|
||||||
|
if (decimal && !ctx.options.flag(CompilationFlag.DecimalMode)) {
|
||||||
|
ErrorReporting.warn("Unsupported decimal operation", ctx.options, v.position)
|
||||||
|
}
|
||||||
|
val env = ctx.env
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
env.eval(addend) match {
|
||||||
|
case Some(NumericConstant(0, _)) => Nil
|
||||||
|
case Some(NumericConstant(1, _)) if !decimal => if (subtract) {
|
||||||
|
simpleOperation(DEC, ctx, v, IndexChoice.RequireX, preserveA = false, commutative = true)
|
||||||
|
} else {
|
||||||
|
simpleOperation(INC, ctx, v, IndexChoice.RequireX, preserveA = false, commutative = true)
|
||||||
|
}
|
||||||
|
// TODO: compile +=2 to two INCs
|
||||||
|
case Some(NumericConstant(-1, _)) if !decimal => if (subtract) {
|
||||||
|
simpleOperation(INC, ctx, v, IndexChoice.RequireX, preserveA = false, commutative = true)
|
||||||
|
} else {
|
||||||
|
simpleOperation(DEC, ctx, v, IndexChoice.RequireX, preserveA = false, commutative = true)
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
val loadLhs = MlCompiler.compile(ctx, v, Some(b -> RegisterVariable(Register.A, b)), NoBranching)
|
||||||
|
val modifyLhs = if (subtract) {
|
||||||
|
insertBeforeLast(AssemblyLine.implied(SEC), simpleOperation(SBC, ctx, addend, IndexChoice.PreferY, preserveA = true, commutative = false))
|
||||||
|
} else {
|
||||||
|
insertBeforeLast(AssemblyLine.implied(CLC), simpleOperation(ADC, ctx, addend, IndexChoice.PreferY, preserveA = true, commutative = true))
|
||||||
|
}
|
||||||
|
val storeLhs = MlCompiler.compileByteStorage(ctx, Register.A, v)
|
||||||
|
wrapInSedCldIfNeeded(decimal, loadLhs ++ modifyLhs ++ storeLhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileInPlaceWordOrLongAddition(ctx: CompilationContext, lhs: LhsExpression, addend: Expression, subtract: Boolean, decimal: Boolean): List[AssemblyLine] = {
|
||||||
|
if (decimal && !ctx.options.flag(CompilationFlag.DecimalMode)) {
|
||||||
|
ErrorReporting.warn("Unsupported decimal operation", ctx.options, lhs.position)
|
||||||
|
}
|
||||||
|
val env = ctx.env
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
val w = env.get[Type]("word")
|
||||||
|
val targetBytes: List[List[AssemblyLine]] = lhs match {
|
||||||
|
case v: VariableExpression =>
|
||||||
|
val variable = env.get[Variable](v.name)
|
||||||
|
List.tabulate(variable.typ.size) { i => AssemblyLine.variable(ctx, STA, variable, i) }
|
||||||
|
case SeparateBytesExpression(h: VariableExpression, l: VariableExpression) =>
|
||||||
|
val lv = env.get[Variable](l.name)
|
||||||
|
val hv = env.get[Variable](h.name)
|
||||||
|
List(
|
||||||
|
AssemblyLine.variable(ctx, STA, lv),
|
||||||
|
AssemblyLine.variable(ctx, STA, hv))
|
||||||
|
}
|
||||||
|
val lhsIsStack = targetBytes.head.head.opcode == TSX
|
||||||
|
val targetSize = targetBytes.size
|
||||||
|
val addendType = MlCompiler.getExpressionType(ctx, addend)
|
||||||
|
var addendSize = addendType.size
|
||||||
|
|
||||||
|
def isRhsComplex(xs: List[AssemblyLine]): Boolean = xs match {
|
||||||
|
case AssemblyLine(LDA, _, _, _) :: Nil => false
|
||||||
|
case AssemblyLine(LDA, _, _, _) :: AssemblyLine(LDX, _, _, _) :: Nil => false
|
||||||
|
case _ => true
|
||||||
|
}
|
||||||
|
|
||||||
|
def isRhsStack(xs: List[AssemblyLine]): Boolean = xs.exists(_.opcode == TSX)
|
||||||
|
|
||||||
|
val (calculateRhs, addendByteRead0): (List[AssemblyLine], List[List[AssemblyLine]]) = env.eval(addend) match {
|
||||||
|
case Some(constant) =>
|
||||||
|
addendSize = targetSize
|
||||||
|
Nil -> List.tabulate(targetSize)(i => List(AssemblyLine.immediate(LDA, constant.subbyte(i))))
|
||||||
|
case None =>
|
||||||
|
addendSize match {
|
||||||
|
case 1 =>
|
||||||
|
val base = MlCompiler.compile(ctx, addend, Some(b -> RegisterVariable(Register.A, b)), NoBranching)
|
||||||
|
if (subtract) {
|
||||||
|
if (isRhsComplex(base)) {
|
||||||
|
if (isRhsStack(base)) {
|
||||||
|
ErrorReporting.warn("Subtracting a stack-based value", ctx.options)
|
||||||
|
???
|
||||||
|
}
|
||||||
|
(base ++ List(AssemblyLine.implied(PHA))) -> List(List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(LDA, 0x101)))
|
||||||
|
} else {
|
||||||
|
Nil -> base.map(_ :: Nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
base -> List(Nil)
|
||||||
|
}
|
||||||
|
case 2 =>
|
||||||
|
val base = MlCompiler.compile(ctx, addend, Some(w -> RegisterVariable(Register.AX, w)), NoBranching)
|
||||||
|
if (isRhsStack(base)) {
|
||||||
|
val fixedBase = MlCompiler.compile(ctx, addend, Some(w -> RegisterVariable(Register.AY, w)), NoBranching)
|
||||||
|
if (subtract) {
|
||||||
|
ErrorReporting.warn("Subtracting a stack-based value", ctx.options)
|
||||||
|
if (isRhsComplex(base)) {
|
||||||
|
???
|
||||||
|
} else {
|
||||||
|
Nil -> fixedBase
|
||||||
|
???
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fixedBase -> List(Nil, List(AssemblyLine.implied(TYA)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (subtract) {
|
||||||
|
if (isRhsComplex(base)) {
|
||||||
|
(base ++ List(
|
||||||
|
AssemblyLine.implied(PHA),
|
||||||
|
AssemblyLine.implied(TXA),
|
||||||
|
AssemblyLine.implied(PHA))
|
||||||
|
) -> List(
|
||||||
|
List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(LDA, 0x102)),
|
||||||
|
List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(LDA, 0x101)))
|
||||||
|
} else {
|
||||||
|
Nil -> base.map(_ :: Nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (lhsIsStack) {
|
||||||
|
val fixedBase = MlCompiler.compile(ctx, addend, Some(w -> RegisterVariable(Register.AY, w)), NoBranching)
|
||||||
|
fixedBase -> List(Nil, List(AssemblyLine.implied(TYA)))
|
||||||
|
} else {
|
||||||
|
base -> List(Nil, List(AssemblyLine.implied(TXA)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ => Nil -> (addend match {
|
||||||
|
case vv: VariableExpression =>
|
||||||
|
val source = env.get[Variable](vv.name)
|
||||||
|
List.tabulate(addendSize)(i => AssemblyLine.variable(ctx, LDA, source, i))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val addendByteRead = addendByteRead0 ++ List.fill((targetSize - addendByteRead0.size) max 0)(List(AssemblyLine.immediate(LDA, 0)))
|
||||||
|
val buffer = mutable.ListBuffer[AssemblyLine]()
|
||||||
|
buffer ++= calculateRhs
|
||||||
|
buffer += AssemblyLine.implied(if (subtract) SEC else CLC)
|
||||||
|
val extendMultipleBytes = targetSize > addendSize + 1
|
||||||
|
val extendAtLeastOneByte = targetSize > addendSize
|
||||||
|
for (i <- 0 until targetSize) {
|
||||||
|
if (subtract) {
|
||||||
|
if (addendSize < targetSize && addendType.isSigned) {
|
||||||
|
// TODO: sign extension
|
||||||
|
???
|
||||||
|
}
|
||||||
|
buffer ++= staTo(LDA, targetBytes(i))
|
||||||
|
buffer ++= ldTo(SBC, addendByteRead(i))
|
||||||
|
buffer ++= targetBytes(i)
|
||||||
|
} else {
|
||||||
|
if (i >= addendSize) {
|
||||||
|
if (addendType.isSigned) {
|
||||||
|
val label = MlCompiler.nextLabel("sx")
|
||||||
|
buffer += AssemblyLine.implied(TXA)
|
||||||
|
if (i == addendSize) {
|
||||||
|
buffer += AssemblyLine.immediate(ORA, 0x7f)
|
||||||
|
buffer += AssemblyLine.relative(BMI, label)
|
||||||
|
buffer += AssemblyLine.immediate(LDA, 0)
|
||||||
|
buffer += AssemblyLine.label(label)
|
||||||
|
if (extendMultipleBytes) buffer += AssemblyLine.implied(TAX)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer += AssemblyLine.immediate(LDA, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer ++= addendByteRead(i)
|
||||||
|
if (addendType.isSigned && i == addendSize - 1 && extendAtLeastOneByte) {
|
||||||
|
buffer += AssemblyLine.implied(TAX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer ++= staTo(ADC, targetBytes(i))
|
||||||
|
buffer ++= targetBytes(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i <- 0 until calculateRhs.count(a => a.opcode == PHA) - calculateRhs.count(a => a.opcode == PLA)) {
|
||||||
|
buffer += AssemblyLine.implied(PLA)
|
||||||
|
}
|
||||||
|
wrapInSedCldIfNeeded(decimal, buffer.toList)
|
||||||
|
}
|
||||||
|
|
||||||
|
def compileInPlaceByteBitOp(ctx: CompilationContext, v: LhsExpression, param: Expression, operation: Opcode.Value): List[AssemblyLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
(operation, env.eval(param)) match {
|
||||||
|
case (EOR, Some(NumericConstant(0, _)))
|
||||||
|
| (ORA, Some(NumericConstant(0, _)))
|
||||||
|
| (AND, Some(NumericConstant(0xff, _)))
|
||||||
|
| (AND, Some(NumericConstant(-1, _))) =>
|
||||||
|
Nil
|
||||||
|
case _ =>
|
||||||
|
val loadLhs = MlCompiler.compile(ctx, v, Some(b -> RegisterVariable(Register.A, b)), NoBranching)
|
||||||
|
val modifyLhs = simpleOperation(operation, ctx, param, IndexChoice.PreferY, preserveA = true, commutative = true)
|
||||||
|
val storeLhs = MlCompiler.compileByteStorage(ctx, Register.A, v)
|
||||||
|
loadLhs ++ modifyLhs ++ storeLhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def compileInPlaceWordOrLongBitOp(ctx: CompilationContext, lhs: LhsExpression, param: Expression, operation: Opcode.Value): List[AssemblyLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val b = env.get[Type]("byte")
|
||||||
|
val w = env.get[Type]("word")
|
||||||
|
val targetBytes: List[List[AssemblyLine]] = lhs match {
|
||||||
|
case v: VariableExpression =>
|
||||||
|
val variable = env.get[Variable](v.name)
|
||||||
|
List.tabulate(variable.typ.size) { i => AssemblyLine.variable(ctx, STA, variable, i) }
|
||||||
|
case SeparateBytesExpression(h: VariableExpression, l: VariableExpression) =>
|
||||||
|
val lv = env.get[Variable](l.name)
|
||||||
|
val hv = env.get[Variable](h.name)
|
||||||
|
List(
|
||||||
|
AssemblyLine.variable(ctx, STA, lv),
|
||||||
|
AssemblyLine.variable(ctx, STA, hv))
|
||||||
|
case _ =>
|
||||||
|
???
|
||||||
|
}
|
||||||
|
val lo = targetBytes.head
|
||||||
|
val targetSize = targetBytes.size
|
||||||
|
val paramType = MlCompiler.getExpressionType(ctx, param)
|
||||||
|
var paramSize = paramType.size
|
||||||
|
val extendMultipleBytes = targetSize > paramSize + 1
|
||||||
|
val extendAtLeastOneByte = targetSize > paramSize
|
||||||
|
val (calculateRhs, addendByteRead) = env.eval(param) match {
|
||||||
|
case Some(constant) =>
|
||||||
|
paramSize = targetSize
|
||||||
|
Nil -> List.tabulate(targetSize)(i => List(AssemblyLine.immediate(LDA, constant.subbyte(i))))
|
||||||
|
case None =>
|
||||||
|
paramSize match {
|
||||||
|
case 1 =>
|
||||||
|
val base = MlCompiler.compile(ctx, param, Some(b -> RegisterVariable(Register.A, b)), NoBranching)
|
||||||
|
base -> List(Nil)
|
||||||
|
case 2 =>
|
||||||
|
val base = MlCompiler.compile(ctx, param, Some(w -> RegisterVariable(Register.AX, w)), NoBranching)
|
||||||
|
base -> List(Nil, List(AssemblyLine.implied(TXA)))
|
||||||
|
case _ => Nil -> (param match {
|
||||||
|
case vv: VariableExpression =>
|
||||||
|
val source = env.get[Variable](vv.name)
|
||||||
|
List.tabulate(paramSize)(i => AssemblyLine.variable(ctx, LDA, source, i))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val AllOnes = (1L << (8 * targetSize)) - 1
|
||||||
|
(operation, env.eval(param)) match {
|
||||||
|
case (EOR, Some(NumericConstant(0, _)))
|
||||||
|
| (ORA, Some(NumericConstant(0, _)))
|
||||||
|
| (AND, Some(NumericConstant(AllOnes, _))) =>
|
||||||
|
Nil
|
||||||
|
case _ =>
|
||||||
|
val buffer = mutable.ListBuffer[AssemblyLine]()
|
||||||
|
buffer ++= calculateRhs
|
||||||
|
for (i <- 0 until targetSize) {
|
||||||
|
if (i < paramSize) {
|
||||||
|
buffer ++= addendByteRead(i)
|
||||||
|
if (paramType.isSigned && i == paramSize - 1 && extendAtLeastOneByte) {
|
||||||
|
buffer += AssemblyLine.implied(TAX)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (paramType.isSigned) {
|
||||||
|
val label = MlCompiler.nextLabel("sx")
|
||||||
|
buffer += AssemblyLine.implied(TXA)
|
||||||
|
if (i == paramSize) {
|
||||||
|
buffer += AssemblyLine.immediate(ORA, 0x7f)
|
||||||
|
buffer += AssemblyLine.relative(BMI, label)
|
||||||
|
buffer += AssemblyLine.immediate(LDA, 0)
|
||||||
|
buffer += AssemblyLine.label(label)
|
||||||
|
if (extendMultipleBytes) buffer += AssemblyLine.implied(TAX)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer += AssemblyLine.immediate(LDA, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer ++= staTo(operation, targetBytes(i))
|
||||||
|
buffer ++= targetBytes(i)
|
||||||
|
}
|
||||||
|
for (i <- 0 until calculateRhs.count(a => a.opcode == PHA) - calculateRhs.count(a => a.opcode == PLA)) {
|
||||||
|
buffer += AssemblyLine.implied(PLA)
|
||||||
|
}
|
||||||
|
buffer.toList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
12
src/main/scala/millfork/compiler/CompilationContext.scala
Normal file
12
src/main/scala/millfork/compiler/CompilationContext.scala
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package millfork.compiler
|
||||||
|
|
||||||
|
import millfork.{CompilationFlag, CompilationOptions}
|
||||||
|
import millfork.env.{Environment, MangledFunction, NormalFunction}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
case class CompilationContext(env: Environment, function: NormalFunction, extraStackOffset: Int, options: CompilationOptions){
|
||||||
|
|
||||||
|
def addStack(i: Int): CompilationContext = this.copy(extraStackOffset = extraStackOffset + i)
|
||||||
|
}
|
1675
src/main/scala/millfork/compiler/MfCompiler.scala
Normal file
1675
src/main/scala/millfork/compiler/MfCompiler.scala
Normal file
File diff suppressed because it is too large
Load Diff
224
src/main/scala/millfork/env/Constant.scala
vendored
Normal file
224
src/main/scala/millfork/env/Constant.scala
vendored
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
package millfork.env
|
||||||
|
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node.Position
|
||||||
|
|
||||||
|
object Constant {
|
||||||
|
val Zero: Constant = NumericConstant(0, 1)
|
||||||
|
val One: Constant = NumericConstant(1, 1)
|
||||||
|
|
||||||
|
def error(msg: String, position: Option[Position] = None): Constant = {
|
||||||
|
ErrorReporting.error(msg, position)
|
||||||
|
Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
def minimumSize(value: Long): Int = if (value < -128 || value > 255) 2 else 1 // TODO !!!
|
||||||
|
}
|
||||||
|
|
||||||
|
import millfork.env.Constant.minimumSize
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node.Position
|
||||||
|
|
||||||
|
sealed trait Constant {
|
||||||
|
|
||||||
|
def asl(i: Constant): Constant = i match {
|
||||||
|
case NumericConstant(sa, _) => asl(sa.toInt)
|
||||||
|
case _ => CompoundConstant(MathOperator.Shl, this, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
def asl(i: Int): Constant = CompoundConstant(MathOperator.Shl, this, NumericConstant(i, 1))
|
||||||
|
|
||||||
|
def requiredSize: Int
|
||||||
|
|
||||||
|
def +(that: Constant): Constant = CompoundConstant(MathOperator.Plus, this, that)
|
||||||
|
|
||||||
|
def -(that: Constant): Constant = CompoundConstant(MathOperator.Minus, this, that)
|
||||||
|
|
||||||
|
def +(that: Long): Constant = if (that == 0) this else this + NumericConstant(that, minimumSize(that))
|
||||||
|
|
||||||
|
def -(that: Long): Constant = this + (-that)
|
||||||
|
|
||||||
|
def loByte: Constant = {
|
||||||
|
if (requiredSize == 1) return this
|
||||||
|
HalfWordConstant(this, hi = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
def hiByte: Constant = {
|
||||||
|
if (requiredSize == 1) Constant.Zero
|
||||||
|
else HalfWordConstant(this, hi = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
def subbyte(index: Int): Constant = {
|
||||||
|
if (requiredSize <= index) Constant.Zero
|
||||||
|
else index match {
|
||||||
|
case 0 => loByte
|
||||||
|
case 1 => hiByte
|
||||||
|
case _ => SubbyteConstant(this, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def isLowestByteAlwaysEqual(i: Int) : Boolean = false
|
||||||
|
|
||||||
|
def quickSimplify: Constant = this
|
||||||
|
}
|
||||||
|
|
||||||
|
case class UnexpandedConstant(name: String, requiredSize: Int) extends Constant
|
||||||
|
|
||||||
|
case class NumericConstant(value: Long, requiredSize: Int) extends Constant {
|
||||||
|
if (requiredSize == 1) {
|
||||||
|
if (value < -128 || value > 255) {
|
||||||
|
throw new IllegalArgumentException("The constant is too big")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def isLowestByteAlwaysEqual(i: Int) : Boolean = (value & 0xff) == (i&0xff)
|
||||||
|
|
||||||
|
override def asl(i: Int) = NumericConstant(value << i, requiredSize + i / 8)
|
||||||
|
|
||||||
|
override def +(that: Constant): Constant = that + value
|
||||||
|
|
||||||
|
override def +(that: Long) = NumericConstant(value + that, minimumSize(value + that))
|
||||||
|
|
||||||
|
override def toString: String = if (value > 9) value.formatted("$%X") else value.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant {
|
||||||
|
override def requiredSize = 2
|
||||||
|
|
||||||
|
override def toString: String = thing.name
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HalfWordConstant(base: Constant, hi: Boolean) extends Constant {
|
||||||
|
override def quickSimplify: Constant = {
|
||||||
|
val simplified = base.quickSimplify
|
||||||
|
simplified match {
|
||||||
|
case NumericConstant(x, size) => if (hi) {
|
||||||
|
if (size == 1) Constant.Zero else NumericConstant((x >> 8) & 0xff, 1)
|
||||||
|
} else {
|
||||||
|
NumericConstant(x & 0xff, 1)
|
||||||
|
}
|
||||||
|
case _ => HalfWordConstant(simplified, hi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def requiredSize = 1
|
||||||
|
|
||||||
|
override def toString: String = base + (if (hi) ".hi" else ".lo")
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SubbyteConstant(base: Constant, index: Int) extends Constant {
|
||||||
|
override def quickSimplify: Constant = {
|
||||||
|
val simplified = base.quickSimplify
|
||||||
|
simplified match {
|
||||||
|
case NumericConstant(x, size) => if (index >= size) {
|
||||||
|
Constant.Zero
|
||||||
|
} else {
|
||||||
|
NumericConstant((x >> (index * 8)) & 0xff, 1)
|
||||||
|
}
|
||||||
|
case _ => SubbyteConstant(simplified, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def requiredSize = 1
|
||||||
|
|
||||||
|
override def toString: String = base + (index match {
|
||||||
|
case 0 => ".lo"
|
||||||
|
case 1 => ".hi"
|
||||||
|
case 2 => ".b2"
|
||||||
|
case 3 => ".b3"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
object MathOperator extends Enumeration {
|
||||||
|
val Plus, Minus, Times, Shl, Shr,
|
||||||
|
DecimalPlus, DecimalMinus, DecimalTimes, DecimalShl, DecimalShr,
|
||||||
|
And, Or, Exor = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Constant) extends Constant {
|
||||||
|
override def quickSimplify: Constant = {
|
||||||
|
val l = lhs.quickSimplify
|
||||||
|
val r = rhs.quickSimplify
|
||||||
|
(l, r) match {
|
||||||
|
case (NumericConstant(lv, ls), NumericConstant(rv, rs)) =>
|
||||||
|
var size = ls max rs
|
||||||
|
val value = operator match {
|
||||||
|
case MathOperator.Plus => lv + rv
|
||||||
|
case MathOperator.Minus => lv - rv
|
||||||
|
case MathOperator.Times => lv * rv
|
||||||
|
case MathOperator.Shl => lv << rv
|
||||||
|
case MathOperator.Shr => lv >> rv
|
||||||
|
case MathOperator.Exor => lv ^ rv
|
||||||
|
case MathOperator.Or => lv | rv
|
||||||
|
case MathOperator.And => lv & rv
|
||||||
|
case _ => return this
|
||||||
|
}
|
||||||
|
operator match {
|
||||||
|
case MathOperator.Times | MathOperator.Shl =>
|
||||||
|
val mask = (1 << (size * 8)) - 1
|
||||||
|
if (value != (value & mask)){
|
||||||
|
size = ls + rs
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
NumericConstant(value, size)
|
||||||
|
case _ => CompoundConstant(operator, l, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
import MathOperator._
|
||||||
|
|
||||||
|
override def +(that: Constant): Constant = {
|
||||||
|
that match {
|
||||||
|
case NumericConstant(n, _) => this + n
|
||||||
|
case _ => super.+(that)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def +(that: Long): Constant = {
|
||||||
|
if (that == 0) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
val That = that
|
||||||
|
val MinusThat = -that
|
||||||
|
this match {
|
||||||
|
case CompoundConstant(Plus, NumericConstant(MinusThat, _), r) => r
|
||||||
|
case CompoundConstant(Plus, l, NumericConstant(MinusThat, _)) => l
|
||||||
|
case CompoundConstant(Plus, NumericConstant(x, _), r) => CompoundConstant(Plus, r, NumericConstant(x + that, minimumSize(x + that)))
|
||||||
|
case CompoundConstant(Plus, l, NumericConstant(x, _)) => CompoundConstant(Plus, l, NumericConstant(x + that, minimumSize(x + that)))
|
||||||
|
case CompoundConstant(Minus, l, NumericConstant(That, _)) => l
|
||||||
|
case _ => CompoundConstant(Plus, this, NumericConstant(that, minimumSize(that)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def plhs = lhs match {
|
||||||
|
case _: NumericConstant | _: MemoryAddressConstant => lhs
|
||||||
|
case _ => "(" + lhs + ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
private def prhs = lhs match {
|
||||||
|
case _: NumericConstant | _: MemoryAddressConstant => rhs
|
||||||
|
case _ => "(" + rhs + ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = {
|
||||||
|
operator match {
|
||||||
|
case Plus => f"$plhs + $prhs"
|
||||||
|
case Minus => f"$plhs - $prhs"
|
||||||
|
case Times => f"$plhs * $prhs"
|
||||||
|
case Shl => f"$plhs << $prhs"
|
||||||
|
case Shr => f"$plhs >> $prhs"
|
||||||
|
case DecimalPlus => f"$plhs +' $prhs"
|
||||||
|
case DecimalMinus => f"$plhs -' $prhs"
|
||||||
|
case DecimalTimes => f"$plhs *' $prhs"
|
||||||
|
case DecimalShl => f"$plhs <<' $prhs"
|
||||||
|
case DecimalShr => f"$plhs >>' $prhs"
|
||||||
|
case And => f"$plhs & $prhs"
|
||||||
|
case Or => f"$plhs | $prhs"
|
||||||
|
case Exor => f"$plhs ^ $prhs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def requiredSize: Int = lhs.requiredSize max rhs.requiredSize
|
||||||
|
}
|
618
src/main/scala/millfork/env/Environment.scala
vendored
Normal file
618
src/main/scala/millfork/env/Environment.scala
vendored
Normal file
|
@ -0,0 +1,618 @@
|
||||||
|
package millfork.env
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
|
import millfork.{CompilationFlag, CompilationOptions}
|
||||||
|
import millfork.assembly.Opcode
|
||||||
|
import millfork.compiler._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node._
|
||||||
|
import millfork.output.VariableAllocator
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
//noinspection NotImplementedCode
|
||||||
|
class Environment(val parent: Option[Environment], val prefix: String) {
|
||||||
|
|
||||||
|
|
||||||
|
private var baseStackOffset = 0x101
|
||||||
|
private val relVarId = new AtomicLong
|
||||||
|
|
||||||
|
def genRelativeVariable(constant: Constant, typ: Type, zeropage: Boolean): RelativeVariable = {
|
||||||
|
val variable = RelativeVariable(".rv__" + relVarId.incrementAndGet().formatted("%06d"), constant, typ, zeropage = zeropage)
|
||||||
|
addThing(variable, None)
|
||||||
|
variable
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def allThings: Environment = {
|
||||||
|
val allThings: Map[String, Thing] = things.values.map {
|
||||||
|
case m: FunctionInMemory =>
|
||||||
|
m.environment.getAllPrefixedThings
|
||||||
|
case m: InlinedFunction =>
|
||||||
|
m.environment.getAllPrefixedThings
|
||||||
|
case _ => Map[String, Thing]()
|
||||||
|
}.fold(things.toMap)(_ ++ _)
|
||||||
|
val e = new Environment(None, "")
|
||||||
|
e.things.clear()
|
||||||
|
e.things ++= allThings
|
||||||
|
e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private def getAllPrefixedThings = {
|
||||||
|
things.toMap.map { case (n, th) => (if (n.startsWith(".")) n else prefix + n, th) }
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAllLocalVariables: List[Variable] = things.values.flatMap {
|
||||||
|
case v: Variable =>
|
||||||
|
Some(v)
|
||||||
|
case _ => None
|
||||||
|
}.toList
|
||||||
|
|
||||||
|
def allPreallocatables: List[PrellocableThing] = things.values.flatMap {
|
||||||
|
case m: NormalFunction => Some(m)
|
||||||
|
case m: InitializedArray => Some(m)
|
||||||
|
case _ => None
|
||||||
|
}.toList
|
||||||
|
|
||||||
|
def allConstants: List[ConstantThing] = things.values.flatMap {
|
||||||
|
case m: NormalFunction => m.environment.allConstants
|
||||||
|
case m: InlinedFunction => m.environment.allConstants
|
||||||
|
case m: ConstantThing => List(m)
|
||||||
|
case _ => Nil
|
||||||
|
}.toList
|
||||||
|
|
||||||
|
def allocateVariables(nf: Option[NormalFunction], callGraph: CallGraph, allocator: VariableAllocator, options: CompilationOptions, onEachVariable: (String, Int) => Unit): Unit = {
|
||||||
|
val b = get[Type]("byte")
|
||||||
|
val p = get[Type]("pointer")
|
||||||
|
var params = nf.fold(List[String]()) { f =>
|
||||||
|
f.params match {
|
||||||
|
case NormalParamSignature(ps) =>
|
||||||
|
ps.map(p => p.name)
|
||||||
|
case _ =>
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}.toSet
|
||||||
|
val toAdd = things.values.flatMap {
|
||||||
|
case m: UninitializedMemory =>
|
||||||
|
val vertex = if (options.flag(CompilationFlag.VariableOverlap)) {
|
||||||
|
nf.fold[VariableVertex](GlobalVertex) { f =>
|
||||||
|
if (m.alloc == VariableAllocationMethod.Static) {
|
||||||
|
GlobalVertex
|
||||||
|
} else if (params(m.name)) {
|
||||||
|
ParamVertex(f.name)
|
||||||
|
} else {
|
||||||
|
LocalVertex(f.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else GlobalVertex
|
||||||
|
m.alloc match {
|
||||||
|
case VariableAllocationMethod.None =>
|
||||||
|
Nil
|
||||||
|
case VariableAllocationMethod.Zeropage =>
|
||||||
|
m.sizeInBytes match {
|
||||||
|
case 2 =>
|
||||||
|
val addr =
|
||||||
|
allocator.allocatePointer(callGraph, vertex)
|
||||||
|
onEachVariable(m.name, addr)
|
||||||
|
List(
|
||||||
|
ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case VariableAllocationMethod.Auto | VariableAllocationMethod.Static =>
|
||||||
|
m.sizeInBytes match {
|
||||||
|
case 0 => Nil
|
||||||
|
case 2 =>
|
||||||
|
val addr =
|
||||||
|
allocator.allocateBytes(callGraph, vertex, options, 2)
|
||||||
|
onEachVariable(m.name, addr)
|
||||||
|
List(
|
||||||
|
ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p)
|
||||||
|
)
|
||||||
|
case count =>
|
||||||
|
val addr = allocator.allocateBytes(callGraph, vertex, options, count)
|
||||||
|
onEachVariable(m.name, addr)
|
||||||
|
List(
|
||||||
|
ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case f: NormalFunction =>
|
||||||
|
f.environment.allocateVariables(Some(f), callGraph, allocator, options, onEachVariable)
|
||||||
|
Nil
|
||||||
|
case _ => Nil
|
||||||
|
}.toList
|
||||||
|
val tagged: List[(String, Thing)] = toAdd.map(x => x.name -> x)
|
||||||
|
things ++= tagged
|
||||||
|
}
|
||||||
|
|
||||||
|
val things: mutable.Map[String, Thing] = mutable.Map()
|
||||||
|
|
||||||
|
private def addThing(t: Thing, position: Option[Position]): Unit = {
|
||||||
|
assertNotDefined(t.name, position)
|
||||||
|
things(t.name.stripPrefix(prefix)) = t
|
||||||
|
}
|
||||||
|
|
||||||
|
def removeVariable(str: String): Unit = {
|
||||||
|
things -= str
|
||||||
|
things -= str + ".addr"
|
||||||
|
}
|
||||||
|
|
||||||
|
def get[T <: Thing : Manifest](name: String, position: Option[Position] = None): T = {
|
||||||
|
val clazz = implicitly[Manifest[T]].runtimeClass
|
||||||
|
if (things.contains(name)) {
|
||||||
|
val t: Thing = things(name)
|
||||||
|
if ((t ne null) && clazz.isInstance(t)) {
|
||||||
|
t.asInstanceOf[T]
|
||||||
|
} else {
|
||||||
|
ErrorReporting.fatal(s"`$name` is not a ${clazz.getSimpleName}", position)
|
||||||
|
}
|
||||||
|
} else parent.fold {
|
||||||
|
ErrorReporting.fatal(s"${clazz.getSimpleName} `$name` is not defined", position)
|
||||||
|
} {
|
||||||
|
_.get[T](name, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def maybeGet[T <: Thing : Manifest](name: String): Option[T] = {
|
||||||
|
if (things.contains(name)) {
|
||||||
|
val t: Thing = things(name)
|
||||||
|
val clazz = implicitly[Manifest[T]].runtimeClass
|
||||||
|
if ((t ne null) && clazz.isInstance(t)) {
|
||||||
|
Some(t.asInstanceOf[T])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else parent.flatMap {
|
||||||
|
_.maybeGet[T](name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getArrayOrPointer(arrayName: String): Thing = {
|
||||||
|
maybeGet[ThingInMemory](arrayName).
|
||||||
|
orElse(maybeGet[ThingInMemory](arrayName + ".array")).
|
||||||
|
orElse(maybeGet[ConstantThing](arrayName)).
|
||||||
|
getOrElse(ErrorReporting.fatal(s"`$arrayName` is not an array or a pointer"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.isEmpty) {
|
||||||
|
addThing(VoidType, None)
|
||||||
|
addThing(BuiltInBooleanType, None)
|
||||||
|
addThing(BasicPlainType("byte", 1), None)
|
||||||
|
addThing(BasicPlainType("word", 2), None)
|
||||||
|
addThing(BasicPlainType("long", 4), None)
|
||||||
|
addThing(DerivedPlainType("pointer", get[PlainType]("word"), isSigned = false), None)
|
||||||
|
addThing(DerivedPlainType("ubyte", get[PlainType]("byte"), isSigned = false), None)
|
||||||
|
addThing(DerivedPlainType("sbyte", get[PlainType]("byte"), isSigned = true), None)
|
||||||
|
addThing(DerivedPlainType("cent", get[PlainType]("byte"), isSigned = false), None)
|
||||||
|
val trueType = ConstantBooleanType("true$", value = true)
|
||||||
|
val falseType = ConstantBooleanType("false$", value = false)
|
||||||
|
addThing(trueType, None)
|
||||||
|
addThing(falseType, None)
|
||||||
|
addThing(ConstantThing("true", NumericConstant(0, 0), trueType), None)
|
||||||
|
addThing(ConstantThing("false", NumericConstant(0, 0), falseType), None)
|
||||||
|
addThing(FlagBooleanType("set_carry", Opcode.BCS, Opcode.BCC), None)
|
||||||
|
addThing(FlagBooleanType("clear_carry", Opcode.BCC, Opcode.BCS), None)
|
||||||
|
addThing(FlagBooleanType("set_overflow", Opcode.BVS, Opcode.BVC), None)
|
||||||
|
addThing(FlagBooleanType("clear_overflow", Opcode.BVC, Opcode.BVS), None)
|
||||||
|
addThing(FlagBooleanType("set_zero", Opcode.BEQ, Opcode.BNE), None)
|
||||||
|
addThing(FlagBooleanType("clear_zero", Opcode.BNE, Opcode.BEQ), None)
|
||||||
|
addThing(FlagBooleanType("set_negative", Opcode.BMI, Opcode.BPL), None)
|
||||||
|
addThing(FlagBooleanType("clear_negative", Opcode.BPL, Opcode.BMI), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
def assertNotDefined(name: String, position: Option[Position]): Unit = {
|
||||||
|
if (things.contains(name) || parent.exists(_.things.contains(name)))
|
||||||
|
ErrorReporting.fatal(s"`$name` is already defined", position)
|
||||||
|
}
|
||||||
|
|
||||||
|
def registerType(stmt: TypeDefinitionStatement): Unit = {
|
||||||
|
// addThing(DerivedPlainType(stmt.name, get(stmt.parent)))
|
||||||
|
???
|
||||||
|
}
|
||||||
|
|
||||||
|
def sequence[A](a: List[Option[A]]): Option[List[A]] = a match {
|
||||||
|
case Nil => Some(Nil)
|
||||||
|
case None :: _ => None
|
||||||
|
case Some(r) :: t => sequence(t) map (r :: _)
|
||||||
|
}
|
||||||
|
|
||||||
|
def evalVariableAndConstantSubParts(e: Expression): (Option[Expression], Constant) =
|
||||||
|
e match {
|
||||||
|
case SumExpression(params, false) =>
|
||||||
|
val (constants, variables) = params.map { case (sign, expr) => (sign, expr, eval(expr)) }.partition(_._3.isDefined)
|
||||||
|
val constant = eval(SumExpression(constants.map(x => (x._1, x._2)), decimal = false)).get
|
||||||
|
val variable = variables match {
|
||||||
|
case Nil => None
|
||||||
|
case List((false, x, _)) => Some(x)
|
||||||
|
case _ => Some(SumExpression(variables.map(x => (x._1, x._2)), decimal = false))
|
||||||
|
}
|
||||||
|
variable -> constant
|
||||||
|
case _ => eval(e) match {
|
||||||
|
case Some(c) => None -> c
|
||||||
|
case None => Some(e) -> Constant.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def eval(e: Expression): Option[Constant] = {
|
||||||
|
e match {
|
||||||
|
case LiteralExpression(value, size) => Some(NumericConstant(value, size))
|
||||||
|
case VariableExpression(name) =>
|
||||||
|
maybeGet[ConstantThing](name).map(_.value)
|
||||||
|
case IndexedExpression(_, _) => None
|
||||||
|
case HalfWordExpression(param, hi) => eval(e).map(c => if (hi) c.hiByte else c.loByte)
|
||||||
|
case SumExpression(params, decimal) =>
|
||||||
|
params.map {
|
||||||
|
case (minus, param) => (minus, eval(param))
|
||||||
|
}.foldLeft(Some(Constant.Zero).asInstanceOf[Option[Constant]]) { (oc, pair) =>
|
||||||
|
oc.flatMap { c =>
|
||||||
|
pair match {
|
||||||
|
case (_, None) => None
|
||||||
|
case (minus, Some(addend)) =>
|
||||||
|
val op = if (decimal) {
|
||||||
|
if (minus) MathOperator.DecimalMinus else MathOperator.DecimalPlus
|
||||||
|
} else {
|
||||||
|
if (minus) MathOperator.Minus else MathOperator.Plus
|
||||||
|
}
|
||||||
|
Some(CompoundConstant(op, c, addend))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case SeparateBytesExpression(h, l) => for {
|
||||||
|
lc <- eval(l)
|
||||||
|
hc <- eval(h)
|
||||||
|
} yield hc.asl(8) + lc
|
||||||
|
case FunctionCallExpression(name, params) =>
|
||||||
|
name match {
|
||||||
|
case "*" =>
|
||||||
|
constantOperation(MathOperator.Times, params)
|
||||||
|
case "&&" | "&" =>
|
||||||
|
constantOperation(MathOperator.And, params)
|
||||||
|
case "^" =>
|
||||||
|
constantOperation(MathOperator.Exor, params)
|
||||||
|
case "||" | "|" =>
|
||||||
|
constantOperation(MathOperator.Or, params)
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def constantOperation(op: MathOperator.Value, params: List[Expression]) = {
|
||||||
|
params.map(eval(_)).reduceLeft[Option[Constant]] { (oc, om) =>
|
||||||
|
for {
|
||||||
|
c <- oc
|
||||||
|
m <- om
|
||||||
|
} yield CompoundConstant(op, c, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def registerFunction(stmt: FunctionDeclarationStatement, options: CompilationOptions): Unit = {
|
||||||
|
val w = get[Type]("word")
|
||||||
|
val name = stmt.name
|
||||||
|
val resultType = get[Type](stmt.resultType)
|
||||||
|
|
||||||
|
if (stmt.reentrant && stmt.interrupt) ErrorReporting.error(s"Reentrant function `$name` cannot be an interrupt handler", stmt.position)
|
||||||
|
if (stmt.reentrant && stmt.params.nonEmpty) ErrorReporting.error(s"Reentrant function `$name` cannot have parameters", stmt.position)
|
||||||
|
if (stmt.interrupt && stmt.params.nonEmpty) ErrorReporting.error(s"Interrupt function `$name` cannot have parameters", stmt.position)
|
||||||
|
if (stmt.inlined) {
|
||||||
|
if (!stmt.assembly) {
|
||||||
|
if (stmt.params.nonEmpty) ErrorReporting.error(s"Inline non-assembly function `$name` cannot have parameters", stmt.position) // TODO: ???
|
||||||
|
if (resultType != VoidType) ErrorReporting.error(s"Inline non-assembly function `$name` must return void", stmt.position)
|
||||||
|
}
|
||||||
|
if (stmt.params.exists(_.assemblyParamPassingConvention.inNonInlinedOnly))
|
||||||
|
ErrorReporting.error(s"Inline function `$name` cannot have by-variable parameters", stmt.position)
|
||||||
|
} else {
|
||||||
|
if (!stmt.assembly) {
|
||||||
|
if (stmt.params.exists(!_.assemblyParamPassingConvention.isInstanceOf[ByVariable]))
|
||||||
|
ErrorReporting.error(s"Non-assembly function `$name` cannot have non-variable parameters", stmt.position)
|
||||||
|
}
|
||||||
|
if (stmt.params.exists(_.assemblyParamPassingConvention.inInlinedOnly))
|
||||||
|
ErrorReporting.error(s"Non-inline function `$name` cannot have inlinable parameters", stmt.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
val env = new Environment(Some(this), name + "$")
|
||||||
|
stmt.params.foreach(p => env.registerParameter(p))
|
||||||
|
val params = if (stmt.assembly) {
|
||||||
|
AssemblyParamSignature(stmt.params.map {
|
||||||
|
pd =>
|
||||||
|
val typ = env.get[Type](pd.typ)
|
||||||
|
pd.assemblyParamPassingConvention match {
|
||||||
|
case ByVariable(vn) =>
|
||||||
|
AssemblyParam(typ, env.get[MemoryVariable](vn), AssemblyParameterPassingBehaviour.Copy)
|
||||||
|
case ByRegister(reg) =>
|
||||||
|
AssemblyParam(typ, RegisterVariable(reg, typ), AssemblyParameterPassingBehaviour.Copy)
|
||||||
|
case ByConstant(vn) =>
|
||||||
|
AssemblyParam(typ, Placeholder(vn, typ), AssemblyParameterPassingBehaviour.ByConstant)
|
||||||
|
case ByReference(vn) =>
|
||||||
|
AssemblyParam(typ, Placeholder(vn, typ), AssemblyParameterPassingBehaviour.ByReference)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
NormalParamSignature(stmt.params.map { pd =>
|
||||||
|
env.get[MemoryVariable](pd.assemblyParamPassingConvention.asInstanceOf[ByVariable].name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stmt.statements match {
|
||||||
|
case None =>
|
||||||
|
stmt.address match {
|
||||||
|
case None =>
|
||||||
|
ErrorReporting.error(s"Extern function `${stmt.name}`needs an address", stmt.position)
|
||||||
|
case Some(a) =>
|
||||||
|
val addr = eval(a).getOrElse(Constant.error(s"Address of `${stmt.name}` is not a constant", stmt.position))
|
||||||
|
val mangled = ExternFunction(
|
||||||
|
name,
|
||||||
|
resultType,
|
||||||
|
params,
|
||||||
|
addr,
|
||||||
|
env
|
||||||
|
)
|
||||||
|
addThing(mangled, stmt.position)
|
||||||
|
registerAddressConstant(mangled, stmt.position)
|
||||||
|
addThing(ConstantThing(name + '`', addr, w), stmt.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
case Some(statements) =>
|
||||||
|
statements.foreach {
|
||||||
|
case v: VariableDeclarationStatement => env.registerVariable(v, options)
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
val executableStatements = statements.flatMap {
|
||||||
|
case e: ExecutableStatement => Some(e)
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
val needsExtraRTS = !stmt.inlined && !stmt.assembly && (statements.isEmpty || !statements.last.isInstanceOf[ReturnStatement])
|
||||||
|
if (stmt.inlined) {
|
||||||
|
val mangled = new InlinedFunction(
|
||||||
|
name,
|
||||||
|
resultType,
|
||||||
|
params,
|
||||||
|
env,
|
||||||
|
executableStatements ++ (if (needsExtraRTS) List(AssemblyStatement.implied(Opcode.RTS, elidable = true)) else Nil),
|
||||||
|
)
|
||||||
|
addThing(mangled, stmt.position)
|
||||||
|
} else {
|
||||||
|
var stackVariablesSize = env.things.values.map {
|
||||||
|
case StackVariable(n, t, _) if !n.contains(".") => t.size
|
||||||
|
case _ => 0
|
||||||
|
}.sum
|
||||||
|
val mangled = NormalFunction(
|
||||||
|
name,
|
||||||
|
resultType,
|
||||||
|
params,
|
||||||
|
env,
|
||||||
|
stackVariablesSize,
|
||||||
|
stmt.address.map(a => this.eval(a).getOrElse(Constant.error(s"Address of `${stmt.name}` is not a constant"))),
|
||||||
|
executableStatements ++ (if (needsExtraRTS) List(ReturnStatement(None)) else Nil),
|
||||||
|
interrupt = stmt.interrupt,
|
||||||
|
reentrant = stmt.reentrant,
|
||||||
|
position = stmt.position
|
||||||
|
)
|
||||||
|
addThing(mangled, stmt.position)
|
||||||
|
registerAddressConstant(mangled, stmt.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def registerAddressConstant(thing: ThingInMemory, position: Option[Position]): Unit = {
|
||||||
|
val addr = thing.toAddress
|
||||||
|
addThing(ConstantThing(thing.name + ".addr", addr, get[Type]("pointer")), position)
|
||||||
|
addThing(ConstantThing(thing.name + ".addr.hi", addr.hiByte, get[Type]("byte")), position)
|
||||||
|
addThing(ConstantThing(thing.name + ".addr.lo", addr.loByte, get[Type]("byte")), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
def registerParameter(stmt: ParameterDeclaration): Unit = {
|
||||||
|
val typ = get[Type](stmt.typ)
|
||||||
|
val b = get[Type]("byte")
|
||||||
|
val p = get[Type]("pointer")
|
||||||
|
stmt.assemblyParamPassingConvention match {
|
||||||
|
case ByVariable(name) =>
|
||||||
|
val zp = typ.name == "pointer" // TODO
|
||||||
|
val v = MemoryVariable(prefix + name, typ, if (zp) VariableAllocationMethod.Zeropage else VariableAllocationMethod.Auto)
|
||||||
|
addThing(v, stmt.position)
|
||||||
|
registerAddressConstant(v, stmt.position)
|
||||||
|
if (typ.size == 2) {
|
||||||
|
val addr = v.toAddress
|
||||||
|
addThing(RelativeVariable(v.name + ".hi", addr + 1, b, zeropage = zp), stmt.position)
|
||||||
|
addThing(RelativeVariable(v.name + ".lo", addr, b, zeropage = zp), stmt.position)
|
||||||
|
}
|
||||||
|
case ByRegister(_) => ()
|
||||||
|
case ByConstant(name) =>
|
||||||
|
val v = ConstantThing(prefix + name, UnexpandedConstant(prefix + name, typ.size), typ)
|
||||||
|
addThing(v, stmt.position)
|
||||||
|
case ByReference(name) =>
|
||||||
|
val addr = UnexpandedConstant(prefix + name, typ.size)
|
||||||
|
val v = RelativeVariable(prefix + name, addr, p, zeropage = false)
|
||||||
|
addThing(v, stmt.position)
|
||||||
|
addThing(RelativeVariable(v.name + ".hi", addr + 1, b, zeropage = false), stmt.position)
|
||||||
|
addThing(RelativeVariable(v.name + ".lo", addr, b, zeropage = false), stmt.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def registerArray(stmt: ArrayDeclarationStatement): Unit = {
|
||||||
|
val b = get[Type]("byte")
|
||||||
|
val p = get[Type]("pointer")
|
||||||
|
stmt.elements match {
|
||||||
|
case None =>
|
||||||
|
stmt.length match {
|
||||||
|
case None => ErrorReporting.error(s"Array `${stmt.name}` without size nor contents", stmt.position)
|
||||||
|
case Some(l) =>
|
||||||
|
val address = stmt.address.map(a => eval(a).getOrElse(ErrorReporting.fatal(s"Array `${stmt.name}` has non-constant address", stmt.position)))
|
||||||
|
val lengthConst = eval(l).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant length", stmt.position))
|
||||||
|
lengthConst match {
|
||||||
|
case NumericConstant(length, _) =>
|
||||||
|
if (length > 0xffff || length < 0) ErrorReporting.error(s"Array `${stmt.name}` has invalid length", stmt.position)
|
||||||
|
val array = address match {
|
||||||
|
case None => UninitializedArray(stmt.name + ".array", length.toInt)
|
||||||
|
case Some(aa) => RelativeArray(stmt.name + ".array", aa, length.toInt)
|
||||||
|
}
|
||||||
|
addThing(array, stmt.position)
|
||||||
|
registerAddressConstant(MemoryVariable(stmt.name, p, VariableAllocationMethod.None), stmt.position)
|
||||||
|
val a = address match {
|
||||||
|
case None => array.toAddress
|
||||||
|
case Some(aa) => aa
|
||||||
|
}
|
||||||
|
addThing(RelativeVariable(stmt.name + ".first", a, b, zeropage = false), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name, a, p), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name + ".hi", a.hiByte, b), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name + ".lo", a.loByte, b), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name + ".array.hi", a.hiByte, b), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name + ".array.lo", a.loByte, b), stmt.position)
|
||||||
|
if (length < 256) {
|
||||||
|
addThing(ConstantThing(stmt.name + ".length", lengthConst, b), stmt.position)
|
||||||
|
}
|
||||||
|
case _ => ErrorReporting.error(s"Array `${stmt.name}` has weird length", stmt.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Some(contents) =>
|
||||||
|
stmt.length match {
|
||||||
|
case None =>
|
||||||
|
case Some(l) =>
|
||||||
|
val lengthConst = eval(l).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant length", stmt.position))
|
||||||
|
lengthConst match {
|
||||||
|
case NumericConstant(ll, _) =>
|
||||||
|
if (ll != contents.length) ErrorReporting.error(s"Array `${stmt.name}` has different declared and actual length", stmt.position)
|
||||||
|
case _ => ErrorReporting.error(s"Array `${stmt.name}` has weird length", stmt.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val length = contents.length
|
||||||
|
if (length > 0xffff || length < 0) ErrorReporting.error(s"Array `${stmt.name}` has invalid length", stmt.position)
|
||||||
|
val address = stmt.address.map(a => eval(a).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant address", stmt.position)))
|
||||||
|
val data = contents.map(x => eval(x).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant contents", stmt.position)))
|
||||||
|
val array = InitializedArray(stmt.name + ".array", address, data)
|
||||||
|
addThing(array, stmt.position)
|
||||||
|
registerAddressConstant(MemoryVariable(stmt.name, p, VariableAllocationMethod.None), stmt.position)
|
||||||
|
val a = address match {
|
||||||
|
case None => array.toAddress
|
||||||
|
case Some(aa) => aa
|
||||||
|
}
|
||||||
|
addThing(RelativeVariable(stmt.name + ".first", a, b, zeropage = false), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name, a, p), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name + ".hi", a.hiByte, b), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name + ".lo", a.loByte, b), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name + ".array.hi", a.hiByte, b), stmt.position)
|
||||||
|
addThing(ConstantThing(stmt.name + ".array.lo", a.loByte, b), stmt.position)
|
||||||
|
if (length < 256) {
|
||||||
|
addThing(ConstantThing(stmt.name + ".length", NumericConstant(length, 1), b), stmt.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def registerVariable(stmt: VariableDeclarationStatement, options: CompilationOptions): Unit = {
|
||||||
|
if (stmt.volatile) {
|
||||||
|
ErrorReporting.warn("`volatile` not yet supported", options)
|
||||||
|
}
|
||||||
|
val name = stmt.name
|
||||||
|
val position = stmt.position
|
||||||
|
if (stmt.stack && parent.isEmpty) {
|
||||||
|
if (stmt.stack && stmt.global) ErrorReporting.error(s"`$name` is static or global and cannot be on stack", position)
|
||||||
|
}
|
||||||
|
val b = get[Type]("byte")
|
||||||
|
val typ = get[PlainType](stmt.typ)
|
||||||
|
if (stmt.typ == "pointer") {
|
||||||
|
// if (stmt.constant) {
|
||||||
|
// ErrorReporting.error(s"Pointer `${stmt.name}` cannot be constant")
|
||||||
|
// }
|
||||||
|
stmt.address.flatMap(eval) match {
|
||||||
|
case Some(NumericConstant(a, _)) =>
|
||||||
|
if ((a & 0xff00) != 0)
|
||||||
|
ErrorReporting.error(s"Pointer `${stmt.name}` cannot be located outside the zero page")
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stmt.constant) {
|
||||||
|
if (stmt.stack) ErrorReporting.error(s"`$name` is a constant and cannot be on stack", position)
|
||||||
|
if (stmt.address.isDefined) ErrorReporting.error(s"`$name` is a constant and cannot have an address", position)
|
||||||
|
if (stmt.initialValue.isEmpty) ErrorReporting.error(s"`$name` is a constant and requires a value", position)
|
||||||
|
val constantValue: Constant = stmt.initialValue.flatMap(eval).getOrElse(Constant.error(s"`$name` has a non-constant value", position))
|
||||||
|
if (constantValue.requiredSize > typ.size) ErrorReporting.error(s"`$name` is has an invalid value: not in the range of `$typ`", position)
|
||||||
|
addThing(ConstantThing(prefix + name, constantValue, typ), stmt.position)
|
||||||
|
if (typ.size == 2) {
|
||||||
|
addThing(ConstantThing(prefix + name + ".hi", constantValue + 1, b), stmt.position)
|
||||||
|
addThing(ConstantThing(prefix + name + ".lo", constantValue, b), stmt.position)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (stmt.stack && stmt.global) ErrorReporting.error(s"`$name` is static or global and cannot be on stack", position)
|
||||||
|
if (stmt.initialValue.isDefined) ErrorReporting.error(s"`$name` is not a constant and cannot have a value", position)
|
||||||
|
if (stmt.stack) {
|
||||||
|
val v = StackVariable(prefix + name, typ, this.baseStackOffset)
|
||||||
|
baseStackOffset += typ.size
|
||||||
|
addThing(v, stmt.position)
|
||||||
|
if (typ.size == 2) {
|
||||||
|
addThing(StackVariable(prefix + name + ".lo", b, baseStackOffset), stmt.position)
|
||||||
|
addThing(StackVariable(prefix + name + ".hi", b, baseStackOffset + 1), stmt.position)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val (v, addr) = stmt.address.fold[(VariableInMemory, Constant)]({
|
||||||
|
val alloc = if (typ.name == "pointer") VariableAllocationMethod.Zeropage else if (stmt.global) VariableAllocationMethod.Static else VariableAllocationMethod.Auto
|
||||||
|
val v = MemoryVariable(prefix + name, typ, alloc)
|
||||||
|
registerAddressConstant(v, stmt.position)
|
||||||
|
(v, v.toAddress)
|
||||||
|
})(a => {
|
||||||
|
val addr = eval(a).getOrElse(Constant.error(s"Address of `$name` has a non-constant value", position))
|
||||||
|
val zp = addr match {
|
||||||
|
case NumericConstant(n, _) => n < 0x100
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
(RelativeVariable(prefix + name, addr, typ, zeropage = zp), addr)
|
||||||
|
})
|
||||||
|
addThing(v, stmt.position)
|
||||||
|
if (!v.isInstanceOf[MemoryVariable]) {
|
||||||
|
addThing(ConstantThing(v.name + "`", addr, b), stmt.position)
|
||||||
|
}
|
||||||
|
if (typ.size == 2) {
|
||||||
|
addThing(RelativeVariable(prefix + name + ".hi", addr + 1, b, zeropage = v.zeropage), stmt.position)
|
||||||
|
addThing(RelativeVariable(prefix + name + ".lo", addr, b, zeropage = v.zeropage), stmt.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def lookup[T <: Thing : Manifest](name: String): Option[T] = {
|
||||||
|
if (things.contains(name)) {
|
||||||
|
maybeGet(name)
|
||||||
|
} else {
|
||||||
|
parent.flatMap(_.lookup[T](name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def lookupFunction(name: String, actualParams: List[(Type, Expression)]): Option[MangledFunction] = {
|
||||||
|
if (things.contains(name)) {
|
||||||
|
val function = get[MangledFunction](name)
|
||||||
|
if (function.params.length != actualParams.length) {
|
||||||
|
ErrorReporting.error(s"Invalid number of parameters for function `$name`", actualParams.headOption.flatMap(_._2.position))
|
||||||
|
}
|
||||||
|
function.params match {
|
||||||
|
case NormalParamSignature(params) =>
|
||||||
|
function.params.types.zip(actualParams).zip(params).foreach { case ((required, (actual, expr)), m) =>
|
||||||
|
if (!actual.isAssignableTo(required)) {
|
||||||
|
ErrorReporting.error(s"Invalid value for parameter `${m.name}` of function `$name`", expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case AssemblyParamSignature(params) =>
|
||||||
|
function.params.types.zip(actualParams).zipWithIndex.foreach { case ((required, (actual, expr)), ix) =>
|
||||||
|
if (!actual.isAssignableTo(required)) {
|
||||||
|
ErrorReporting.error(s"Invalid value for parameter ${ix + 1} of function `$name`", expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(function)
|
||||||
|
} else {
|
||||||
|
parent.flatMap(_.lookupFunction(name, actualParams))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def collectDeclarations(program: Program, options: CompilationOptions): Unit = {
|
||||||
|
program.declarations.foreach {
|
||||||
|
case f: FunctionDeclarationStatement => registerFunction(f, options)
|
||||||
|
case v: VariableDeclarationStatement => registerVariable(v, options)
|
||||||
|
case a: ArrayDeclarationStatement => registerArray(a)
|
||||||
|
case i: ImportStatement => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
264
src/main/scala/millfork/env/Thing.scala
vendored
Normal file
264
src/main/scala/millfork/env/Thing.scala
vendored
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
package millfork.env
|
||||||
|
|
||||||
|
import millfork.assembly.Opcode
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node._
|
||||||
|
|
||||||
|
sealed trait Thing {
|
||||||
|
def name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait Type extends Thing {
|
||||||
|
|
||||||
|
def size: Int
|
||||||
|
|
||||||
|
def isSigned: Boolean
|
||||||
|
|
||||||
|
def isSubtypeOf(other: Type): Boolean = this == other
|
||||||
|
|
||||||
|
def isCompatible(other: Type): Boolean = this == other
|
||||||
|
|
||||||
|
override def toString(): String = name
|
||||||
|
|
||||||
|
def isAssignableTo(targetType: Type): Boolean = isCompatible(targetType)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object VoidType extends Type {
|
||||||
|
def size = 0
|
||||||
|
|
||||||
|
def isSigned = false
|
||||||
|
|
||||||
|
override def name = "void"
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait PlainType extends Type {
|
||||||
|
override def isCompatible(other: Type): Boolean = this == other || this.isSubtypeOf(other) || other.isSubtypeOf(this)
|
||||||
|
|
||||||
|
override def isAssignableTo(targetType: Type): Boolean = isCompatible(targetType) || (targetType match {
|
||||||
|
case BasicPlainType(_, size) => size > this.size // TODO
|
||||||
|
case _ => false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case class BasicPlainType(name: String, size: Int) extends PlainType {
|
||||||
|
def isSigned = false
|
||||||
|
|
||||||
|
override def isSubtypeOf(other: Type): Boolean = this == other
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DerivedPlainType(name: String, parent: PlainType, isSigned: Boolean) extends PlainType {
|
||||||
|
def size: Int = parent.size
|
||||||
|
|
||||||
|
override def isSubtypeOf(other: Type): Boolean = parent == other || parent.isSubtypeOf(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait BooleanType extends Type {
|
||||||
|
def size = 0
|
||||||
|
|
||||||
|
def isSigned = false
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ConstantBooleanType(name: String, value: Boolean) extends BooleanType
|
||||||
|
|
||||||
|
case class FlagBooleanType(name: String, jumpIfTrue: Opcode.Value, jumpIfFalse: Opcode.Value) extends BooleanType
|
||||||
|
|
||||||
|
case object BuiltInBooleanType extends BooleanType {
|
||||||
|
override def name = "bool$"
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait TypedThing extends Thing {
|
||||||
|
def typ: Type
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sealed trait ThingInMemory extends Thing {
|
||||||
|
def toAddress: Constant
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait PrellocableThing extends ThingInMemory {
|
||||||
|
def shouldGenerate: Boolean
|
||||||
|
|
||||||
|
def address: Option[Constant]
|
||||||
|
|
||||||
|
def toAddress: Constant = address.getOrElse(MemoryAddressConstant(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Label(name: String) extends ThingInMemory {
|
||||||
|
override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait Variable extends TypedThing
|
||||||
|
|
||||||
|
case class BlackHole(typ: Type) extends Variable {
|
||||||
|
override def name = "<black hole>"
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait VariableInMemory extends Variable with ThingInMemory {
|
||||||
|
|
||||||
|
def zeropage: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
case class RegisterVariable(register: Register.Value, typ: Type) extends Variable {
|
||||||
|
def name: String = register.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Placeholder(name: String, typ: Type) extends Variable
|
||||||
|
|
||||||
|
sealed trait UninitializedMemory extends ThingInMemory {
|
||||||
|
def sizeInBytes: Int
|
||||||
|
|
||||||
|
def alloc: VariableAllocationMethod.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
object VariableAllocationMethod extends Enumeration {
|
||||||
|
val Auto, Static, Zeropage, None = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
case class StackVariable(name: String, typ: Type, baseOffset: Int) extends Variable {
|
||||||
|
def sizeInBytes: Int = typ.size
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MemoryVariable(name: String, typ: Type, alloc: VariableAllocationMethod.Value) extends VariableInMemory with UninitializedMemory {
|
||||||
|
override def sizeInBytes: Int = typ.size
|
||||||
|
|
||||||
|
override def zeropage: Boolean = alloc == VariableAllocationMethod.Zeropage
|
||||||
|
|
||||||
|
override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait MlArray extends ThingInMemory
|
||||||
|
|
||||||
|
case class UninitializedArray(name: String, sizeInBytes: Int) extends MlArray with UninitializedMemory {
|
||||||
|
override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this)
|
||||||
|
|
||||||
|
override def alloc = VariableAllocationMethod.Static
|
||||||
|
}
|
||||||
|
|
||||||
|
case class RelativeArray(name: String, address: Constant, sizeInBytes: Int) extends MlArray {
|
||||||
|
override def toAddress: Constant = address
|
||||||
|
}
|
||||||
|
|
||||||
|
case class InitializedArray(name: String, address: Option[Constant], contents: List[Constant]) extends MlArray with PrellocableThing {
|
||||||
|
override def shouldGenerate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case class RelativeVariable(name: String, address: Constant, typ: Type, zeropage: Boolean) extends VariableInMemory {
|
||||||
|
override def toAddress: Constant = address
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sealed trait MangledFunction extends Thing {
|
||||||
|
def name: String
|
||||||
|
|
||||||
|
def returnType: Type
|
||||||
|
|
||||||
|
def params: ParamSignature
|
||||||
|
|
||||||
|
def interrupt: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
case class EmptyFunction(name: String,
|
||||||
|
returnType: Type,
|
||||||
|
paramType: Type) extends MangledFunction {
|
||||||
|
override def params = EmptyFunctionParamSignature(paramType)
|
||||||
|
|
||||||
|
override def interrupt = false
|
||||||
|
}
|
||||||
|
|
||||||
|
case class InlinedFunction(name: String,
|
||||||
|
returnType: Type,
|
||||||
|
params: ParamSignature,
|
||||||
|
environment: Environment,
|
||||||
|
code: List[ExecutableStatement]) extends MangledFunction {
|
||||||
|
override def interrupt = false
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait FunctionInMemory extends MangledFunction with ThingInMemory {
|
||||||
|
def environment: Environment
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ExternFunction(name: String,
|
||||||
|
returnType: Type,
|
||||||
|
params: ParamSignature,
|
||||||
|
address: Constant,
|
||||||
|
environment: Environment) extends FunctionInMemory {
|
||||||
|
override def toAddress: Constant = address
|
||||||
|
|
||||||
|
override def interrupt = false
|
||||||
|
}
|
||||||
|
|
||||||
|
case class NormalFunction(name: String,
|
||||||
|
returnType: Type,
|
||||||
|
params: ParamSignature,
|
||||||
|
environment: Environment,
|
||||||
|
stackVariablesSize: Int,
|
||||||
|
address: Option[Constant],
|
||||||
|
code: List[ExecutableStatement],
|
||||||
|
interrupt: Boolean,
|
||||||
|
reentrant: Boolean,
|
||||||
|
position: Option[Position]) extends FunctionInMemory with PrellocableThing {
|
||||||
|
override def shouldGenerate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ConstantThing(name: String, value: Constant, typ: Type) extends TypedThing
|
||||||
|
|
||||||
|
trait ParamSignature {
|
||||||
|
def types: List[Type]
|
||||||
|
|
||||||
|
def length: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
case class NormalParamSignature(params: List[MemoryVariable]) extends ParamSignature {
|
||||||
|
override def length: Int = params.length
|
||||||
|
|
||||||
|
override def types: List[Type] = params.map(_.typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait ParamPassingConvention {
|
||||||
|
def inInlinedOnly: Boolean
|
||||||
|
|
||||||
|
def inNonInlinedOnly: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ByRegister(register: Register.Value) extends ParamPassingConvention {
|
||||||
|
override def inInlinedOnly = false
|
||||||
|
|
||||||
|
override def inNonInlinedOnly = false
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ByVariable(name: String) extends ParamPassingConvention {
|
||||||
|
override def inInlinedOnly = false
|
||||||
|
|
||||||
|
override def inNonInlinedOnly = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ByConstant(name: String) extends ParamPassingConvention {
|
||||||
|
override def inInlinedOnly = true
|
||||||
|
|
||||||
|
override def inNonInlinedOnly = false
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ByReference(name: String) extends ParamPassingConvention {
|
||||||
|
override def inInlinedOnly = true
|
||||||
|
|
||||||
|
override def inNonInlinedOnly = false
|
||||||
|
}
|
||||||
|
|
||||||
|
object AssemblyParameterPassingBehaviour extends Enumeration {
|
||||||
|
val Copy, ByReference, ByConstant = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
case class AssemblyParam(typ: Type, variable: TypedThing, behaviour: AssemblyParameterPassingBehaviour.Value)
|
||||||
|
|
||||||
|
|
||||||
|
case class AssemblyParamSignature(params: List[AssemblyParam]) extends ParamSignature {
|
||||||
|
override def length: Int = params.length
|
||||||
|
|
||||||
|
override def types: List[Type] = params.map(_.typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class EmptyFunctionParamSignature(paramType: Type) extends ParamSignature {
|
||||||
|
override def length: Int = 1
|
||||||
|
|
||||||
|
override def types: List[Type] = List(paramType)
|
||||||
|
}
|
74
src/main/scala/millfork/error/ErrorReporting.scala
Normal file
74
src/main/scala/millfork/error/ErrorReporting.scala
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package millfork.error
|
||||||
|
|
||||||
|
import millfork.{CompilationFlag, CompilationOptions}
|
||||||
|
import millfork.node.Position
|
||||||
|
|
||||||
|
object ErrorReporting {
|
||||||
|
|
||||||
|
var verbosity = 0
|
||||||
|
|
||||||
|
var hasErrors = false
|
||||||
|
|
||||||
|
def f(position: Option[Position]): String = position.fold("")(p => s"(${p.line}:${p.column}) ")
|
||||||
|
|
||||||
|
def info(msg: String, position: Option[Position] = None): Unit = {
|
||||||
|
if (verbosity < 0) return
|
||||||
|
println("INFO: " + f(position) + msg)
|
||||||
|
flushOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
def debug(msg: String, position: Option[Position] = None): Unit = {
|
||||||
|
if (verbosity < 1) return
|
||||||
|
println("DEBUG: " + f(position) + msg)
|
||||||
|
flushOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
def trace(msg: String, position: Option[Position] = None): Unit = {
|
||||||
|
if (verbosity < 2) return
|
||||||
|
println("TRACE: " + f(position) + msg)
|
||||||
|
flushOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
private def flushOutput(): Unit = {
|
||||||
|
System.out.flush()
|
||||||
|
System.err.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
def warn(msg: String, options: CompilationOptions, position: Option[Position] = None): Unit = {
|
||||||
|
if (verbosity < 0) return
|
||||||
|
println("WARN: " + f(position) + msg)
|
||||||
|
flushOutput()
|
||||||
|
if (options.flag(CompilationFlag.FatalWarnings)) {
|
||||||
|
hasErrors = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def error(msg: String, position: Option[Position] = None): Unit = {
|
||||||
|
hasErrors = true
|
||||||
|
println("ERROR: " + f(position) + msg)
|
||||||
|
flushOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
def fatal(msg: String, position: Option[Position] = None): Nothing = {
|
||||||
|
hasErrors = true
|
||||||
|
println("FATAL: " + f(position) + msg)
|
||||||
|
flushOutput()
|
||||||
|
throw new RuntimeException(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
def fatalQuit(msg: String, position: Option[Position] = None): Nothing = {
|
||||||
|
hasErrors = true
|
||||||
|
println("FATAL: " + f(position) + msg)
|
||||||
|
flushOutput()
|
||||||
|
System.exit(1)
|
||||||
|
throw new RuntimeException(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
def assertNoErrors(msg: String): Unit = {
|
||||||
|
if (hasErrors) {
|
||||||
|
error(msg)
|
||||||
|
fatal("Build halted due to previous errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
151
src/main/scala/millfork/node/CallGraph.scala
Normal file
151
src/main/scala/millfork/node/CallGraph.scala
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
package millfork.node
|
||||||
|
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
sealed trait VariableVertex {
|
||||||
|
def function: String
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ParamVertex(function: String) extends VariableVertex
|
||||||
|
|
||||||
|
case class LocalVertex(function: String) extends VariableVertex
|
||||||
|
|
||||||
|
case object GlobalVertex extends VariableVertex {
|
||||||
|
override def function = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
trait CallGraph {
|
||||||
|
def canOverlap(a: VariableVertex, b: VariableVertex): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
object RestrictiveCallGraph extends CallGraph {
|
||||||
|
|
||||||
|
def canOverlap(a: VariableVertex, b: VariableVertex): Boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
|
class StandardCallGraph(program: Program) extends CallGraph {
|
||||||
|
|
||||||
|
private val entryPoints = mutable.Set[String]()
|
||||||
|
// (F,G) means function F calls function G
|
||||||
|
private val callEdges = mutable.Set[(String, String)]()
|
||||||
|
// (F,G) means function G is called when building parameters for function F
|
||||||
|
private val paramEdges = mutable.Set[(String, String)]()
|
||||||
|
private val multiaccessibleFunctions = mutable.Set[String]()
|
||||||
|
private val everCalledFunctions = mutable.Set[String]()
|
||||||
|
private val allFunctions = mutable.Set[String]()
|
||||||
|
|
||||||
|
entryPoints += "main"
|
||||||
|
program.declarations.foreach(s => add(None, Nil, s))
|
||||||
|
everCalledFunctions.retain(allFunctions)
|
||||||
|
|
||||||
|
def add(currentFunction: Option[String], callingFunctions: List[String], node: Node): Unit = {
|
||||||
|
node match {
|
||||||
|
case f: FunctionDeclarationStatement =>
|
||||||
|
allFunctions += f.name
|
||||||
|
if (f.address.isDefined || f.interrupt) entryPoints += f.name
|
||||||
|
f.statements.getOrElse(Nil).foreach(s => this.add(Some(f.name), Nil, s))
|
||||||
|
case s: Statement =>
|
||||||
|
s.getAllExpressions.foreach(e => add(currentFunction, callingFunctions, e))
|
||||||
|
case g: FunctionCallExpression =>
|
||||||
|
everCalledFunctions += g.functionName
|
||||||
|
currentFunction.foreach(f => callEdges += f -> g.functionName)
|
||||||
|
callingFunctions.foreach(f => paramEdges += f -> g.functionName)
|
||||||
|
g.expressions.foreach(expr => add(currentFunction, g.functionName :: callingFunctions, expr))
|
||||||
|
case x: VariableExpression =>
|
||||||
|
val varName = x.name.stripSuffix(".hi").stripSuffix(".lo").stripSuffix(".addr")
|
||||||
|
everCalledFunctions += varName
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fillOut(): Unit = {
|
||||||
|
var changed = true
|
||||||
|
while (changed) {
|
||||||
|
changed = false
|
||||||
|
val toAdd = for {
|
||||||
|
(a, b) <- callEdges
|
||||||
|
(c, d) <- callEdges
|
||||||
|
if b == c
|
||||||
|
if !callEdges.contains(a -> d)
|
||||||
|
} yield (a, d)
|
||||||
|
if (toAdd.nonEmpty) {
|
||||||
|
callEdges ++= toAdd
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changed = true
|
||||||
|
while (changed) {
|
||||||
|
changed = false
|
||||||
|
val toAdd = for {
|
||||||
|
(a, b) <- paramEdges
|
||||||
|
(c, d) <- callEdges
|
||||||
|
if b == c
|
||||||
|
if !paramEdges.contains(a -> d)
|
||||||
|
} yield (a, d)
|
||||||
|
if (toAdd.nonEmpty) {
|
||||||
|
paramEdges ++= toAdd
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
multiaccessibleFunctions ++= entryPoints
|
||||||
|
everCalledFunctions ++= entryPoints
|
||||||
|
callEdges.filter(e => entryPoints.contains(e._1)).foreach(e => everCalledFunctions += e._2)
|
||||||
|
multiaccessibleFunctions ++= callEdges.filter(e => entryPoints.contains(e._1)).map(_._2).groupBy(identity).filter(p => p._2.size > 1).keys
|
||||||
|
|
||||||
|
ErrorReporting.trace("Call edges:")
|
||||||
|
callEdges.toList.sorted.foreach(s => ErrorReporting.trace(s.toString))
|
||||||
|
|
||||||
|
ErrorReporting.trace("Param edges:")
|
||||||
|
paramEdges.toList.sorted.foreach(s => ErrorReporting.trace(s.toString))
|
||||||
|
|
||||||
|
ErrorReporting.trace("Entry points:")
|
||||||
|
entryPoints.toList.sorted.foreach(ErrorReporting.trace(_))
|
||||||
|
|
||||||
|
ErrorReporting.trace("Multiaccessible functions:")
|
||||||
|
multiaccessibleFunctions.toList.sorted.foreach(ErrorReporting.trace(_))
|
||||||
|
|
||||||
|
ErrorReporting.trace("Ever called functions:")
|
||||||
|
everCalledFunctions.toList.sorted.foreach(ErrorReporting.trace(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
def isEverCalled(function: String): Boolean = {
|
||||||
|
everCalledFunctions(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
def canOverlap(a: VariableVertex, b: VariableVertex): Boolean = {
|
||||||
|
if (a.function == b.function) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (a == GlobalVertex || b == GlobalVertex) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (multiaccessibleFunctions(a.function) || multiaccessibleFunctions(b.function)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (callEdges(a.function -> b.function) || callEdges(b.function -> a.function)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
a match {
|
||||||
|
case ParamVertex(af) =>
|
||||||
|
if (paramEdges(af -> b.function)) return false
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
b match {
|
||||||
|
case ParamVertex(bf) =>
|
||||||
|
if (paramEdges(bf -> a.function)) return false
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
ErrorReporting.trace(s"$a and $b can overlap")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
181
src/main/scala/millfork/node/Node.scala
Normal file
181
src/main/scala/millfork/node/Node.scala
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
package millfork.node
|
||||||
|
|
||||||
|
import millfork.assembly.{AddrMode, Opcode}
|
||||||
|
import millfork.env.{Label, ParamPassingConvention}
|
||||||
|
|
||||||
|
case class Position(filename: String, line: Int, column: Int, cursor: Int)
|
||||||
|
|
||||||
|
sealed trait Node {
|
||||||
|
var position: Option[Position] = None
|
||||||
|
}
|
||||||
|
|
||||||
|
object Node {
|
||||||
|
implicit class NodeOps[N<:Node](val node: N) extends AnyVal {
|
||||||
|
def pos(position: Position): N = {
|
||||||
|
node.position = Some(position)
|
||||||
|
node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait Expression extends Node {
|
||||||
|
def replaceVariable(variable: String, actualParam: Expression): Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
case class LiteralExpression(value: Long, requiredSize: Int) extends Expression {
|
||||||
|
override def replaceVariable(variable: String, actualParam: Expression): Expression = this
|
||||||
|
}
|
||||||
|
|
||||||
|
case class BooleanLiteralExpression(value: Boolean) extends Expression {
|
||||||
|
override def replaceVariable(variable: String, actualParam: Expression): Expression = this
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait LhsExpression extends Expression
|
||||||
|
|
||||||
|
case object BlackHoleExpression extends LhsExpression {
|
||||||
|
override def replaceVariable(variable: String, actualParam: Expression): LhsExpression = this
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SeparateBytesExpression(hi: Expression, lo: Expression) extends LhsExpression {
|
||||||
|
def replaceVariable(variable: String, actualParam: Expression): Expression =
|
||||||
|
SeparateBytesExpression(
|
||||||
|
hi.replaceVariable(variable, actualParam),
|
||||||
|
lo.replaceVariable(variable, actualParam))
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SumExpression(expressions: List[(Boolean, Expression)], decimal: Boolean) extends Expression {
|
||||||
|
override def replaceVariable(variable: String, actualParam: Expression): Expression =
|
||||||
|
SumExpression(expressions.map { case (n, e) => n -> e.replaceVariable(variable, actualParam) }, decimal)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class FunctionCallExpression(functionName: String, expressions: List[Expression]) extends Expression {
|
||||||
|
override def replaceVariable(variable: String, actualParam: Expression): Expression =
|
||||||
|
FunctionCallExpression(functionName, expressions.map {
|
||||||
|
_.replaceVariable(variable, actualParam)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case class HalfWordExpression(expression: Expression, hiByte: Boolean) extends Expression {
|
||||||
|
override def replaceVariable(variable: String, actualParam: Expression): Expression =
|
||||||
|
HalfWordExpression(expression.replaceVariable(variable, actualParam), hiByte)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Register extends Enumeration {
|
||||||
|
val A, X, Y, AX, AY, YA, XA, XY, YX = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
//case class Indexing(child: Expression, register: Register.Value) extends Expression
|
||||||
|
|
||||||
|
case class VariableExpression(name: String) extends LhsExpression {
|
||||||
|
override def replaceVariable(variable: String, actualParam: Expression): Expression =
|
||||||
|
if (name == variable) actualParam else this
|
||||||
|
}
|
||||||
|
|
||||||
|
case class IndexedExpression(name: String, index: Expression) extends LhsExpression {
|
||||||
|
override def replaceVariable(variable: String, actualParam: Expression): Expression =
|
||||||
|
if (name == variable) {
|
||||||
|
actualParam match {
|
||||||
|
case VariableExpression(actualVariable) => IndexedExpression(actualVariable, index.replaceVariable(variable, actualParam))
|
||||||
|
case _ => ??? // TODO
|
||||||
|
}
|
||||||
|
} else IndexedExpression(name, index.replaceVariable(variable, actualParam))
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait Statement extends Node {
|
||||||
|
def getAllExpressions: List[Expression]
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait DeclarationStatement extends Statement
|
||||||
|
|
||||||
|
case class TypeDefinitionStatement(name: String, parent: String) extends DeclarationStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case class VariableDeclarationStatement(name: String,
|
||||||
|
typ: String,
|
||||||
|
global: Boolean,
|
||||||
|
stack: Boolean,
|
||||||
|
constant: Boolean,
|
||||||
|
volatile: Boolean,
|
||||||
|
initialValue: Option[Expression],
|
||||||
|
address: Option[Expression]) extends DeclarationStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = List(initialValue, address).flatten
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ArrayDeclarationStatement(name: String,
|
||||||
|
length: Option[Expression],
|
||||||
|
address: Option[Expression],
|
||||||
|
elements: Option[List[Expression]]) extends DeclarationStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = List(length, address).flatten ++ elements.getOrElse(Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ParameterDeclaration(typ: String,
|
||||||
|
assemblyParamPassingConvention: ParamPassingConvention) extends Node
|
||||||
|
|
||||||
|
case class ImportStatement(filename: String) extends DeclarationStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case class FunctionDeclarationStatement(name: String,
|
||||||
|
resultType: String,
|
||||||
|
params: List[ParameterDeclaration],
|
||||||
|
address: Option[Expression],
|
||||||
|
statements: Option[List[Statement]],
|
||||||
|
inlined: Boolean,
|
||||||
|
assembly: Boolean,
|
||||||
|
interrupt: Boolean,
|
||||||
|
reentrant: Boolean) extends DeclarationStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = address.toList ++ statements.getOrElse(Nil).flatMap(_.getAllExpressions)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait ExecutableStatement extends Statement
|
||||||
|
|
||||||
|
case class ExpressionStatement(expression: Expression) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = List(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ReturnStatement(value: Option[Expression]) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = value.toList
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Assignment(destination: LhsExpression, source: Expression) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = List(destination, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class LabelStatement(label: Label) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case class AssemblyStatement(opcode: Opcode.Value, addrMode: AddrMode.Value, expression: Expression, elidable: Boolean) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = List(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class IfStatement(condition: Expression, thenBranch: List[ExecutableStatement], elseBranch: List[ExecutableStatement]) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = condition :: (thenBranch ++ elseBranch).flatMap(_.getAllExpressions)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class WhileStatement(condition: Expression, body: List[ExecutableStatement]) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = condition :: body.flatMap(_.getAllExpressions)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ForDirection extends Enumeration {
|
||||||
|
val To, Until, DownTo, ParallelTo, ParallelUntil = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ForStatement(variable: String, start: Expression, end: Expression, direction: ForDirection.Value, body: List[ExecutableStatement]) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = start :: end :: body.flatMap(_.getAllExpressions)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DoWhileStatement(body: List[ExecutableStatement], condition: Expression) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = condition :: body.flatMap(_.getAllExpressions)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class BlockStatement(body: List[ExecutableStatement]) extends ExecutableStatement {
|
||||||
|
override def getAllExpressions: List[Expression] = body.flatMap(_.getAllExpressions)
|
||||||
|
}
|
||||||
|
|
||||||
|
object AssemblyStatement {
|
||||||
|
def implied(opcode: Opcode.Value, elidable: Boolean) = AssemblyStatement(opcode, AddrMode.Implied, LiteralExpression(0, 1), elidable)
|
||||||
|
|
||||||
|
def nonexistent(opcode: Opcode.Value) = AssemblyStatement(opcode, AddrMode.DoesNotExist, LiteralExpression(0, 1), elidable = true)
|
||||||
|
}
|
11
src/main/scala/millfork/node/Program.scala
Normal file
11
src/main/scala/millfork/node/Program.scala
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package millfork.node
|
||||||
|
|
||||||
|
import millfork.node.opt.NodeOptimization
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
case class Program(declarations: List[DeclarationStatement]) {
|
||||||
|
def applyNodeOptimization(o: NodeOptimization) = Program(o.optimize(declarations).asInstanceOf[List[DeclarationStatement]])
|
||||||
|
def +(p:Program): Program = Program(this.declarations ++ p.declarations)
|
||||||
|
}
|
16
src/main/scala/millfork/node/opt/NodeOptimization.scala
Normal file
16
src/main/scala/millfork/node/opt/NodeOptimization.scala
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package millfork.node.opt
|
||||||
|
|
||||||
|
import millfork.node.{ExecutableStatement, Expression, Node, Statement}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
trait NodeOptimization {
|
||||||
|
def optimize(nodes: List[Node]): List[Node]
|
||||||
|
|
||||||
|
def optimizeExecutableStatements(nodes: List[ExecutableStatement]): List[ExecutableStatement] =
|
||||||
|
optimize(nodes).asInstanceOf[List[ExecutableStatement]]
|
||||||
|
|
||||||
|
def optimizeStatements(nodes: List[Statement]): List[Statement] =
|
||||||
|
optimize(nodes).asInstanceOf[List[Statement]]
|
||||||
|
}
|
29
src/main/scala/millfork/node/opt/UnreachableCode.scala
Normal file
29
src/main/scala/millfork/node/opt/UnreachableCode.scala
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package millfork.node.opt
|
||||||
|
|
||||||
|
import millfork.node._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object UnreachableCode extends NodeOptimization {
|
||||||
|
|
||||||
|
override def optimize(nodes: List[Node]): List[Node] = nodes match {
|
||||||
|
case (x:FunctionDeclarationStatement)::xs =>
|
||||||
|
x.copy(statements = x.statements.map(optimizeStatements)) :: optimize(xs)
|
||||||
|
case (x:IfStatement)::xs =>
|
||||||
|
x.copy(
|
||||||
|
thenBranch = optimizeExecutableStatements(x.thenBranch),
|
||||||
|
elseBranch = optimizeExecutableStatements(x.elseBranch)) :: optimize(xs)
|
||||||
|
case (x:WhileStatement)::xs =>
|
||||||
|
x.copy(body = optimizeExecutableStatements(x.body)) :: optimize(xs)
|
||||||
|
case (x:DoWhileStatement)::xs =>
|
||||||
|
x.copy(body = optimizeExecutableStatements(x.body)) :: optimize(xs)
|
||||||
|
case (x:ReturnStatement) :: xs =>
|
||||||
|
x :: Nil
|
||||||
|
case x :: xs =>
|
||||||
|
x :: optimize(xs)
|
||||||
|
case Nil =>
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
src/main/scala/millfork/node/opt/UnusedFunctions.scala
Normal file
72
src/main/scala/millfork/node/opt/UnusedFunctions.scala
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package millfork.node.opt
|
||||||
|
|
||||||
|
import millfork.env._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object UnusedFunctions extends NodeOptimization {
|
||||||
|
|
||||||
|
override def optimize(nodes: List[Node]): List[Node] = {
|
||||||
|
val allNormalFunctions = nodes.flatMap {
|
||||||
|
case v: FunctionDeclarationStatement => if (v.address.isDefined || v.interrupt || v.name == "main") Nil else List(v.name)
|
||||||
|
case _ => Nil
|
||||||
|
}.toSet
|
||||||
|
val allCalledFunctions = getAllCalledFunctions(nodes).toSet
|
||||||
|
val unusedFunctions = allNormalFunctions -- allCalledFunctions
|
||||||
|
if (unusedFunctions.nonEmpty) {
|
||||||
|
ErrorReporting.debug("Removing unused functions: " + unusedFunctions.mkString(", "))
|
||||||
|
}
|
||||||
|
removeFunctionsFromProgram(nodes, unusedFunctions)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def removeFunctionsFromProgram(nodes: List[Node], unusedVariables: Set[String]): List[Node] = {
|
||||||
|
nodes match {
|
||||||
|
case (x: FunctionDeclarationStatement) :: xs if unusedVariables(x.name) =>
|
||||||
|
removeFunctionsFromProgram(xs, unusedVariables)
|
||||||
|
case x :: xs =>
|
||||||
|
x :: removeFunctionsFromProgram(xs, unusedVariables)
|
||||||
|
case Nil =>
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAllCalledFunctions(c: Constant): List[String] = c match {
|
||||||
|
case HalfWordConstant(cc, _) => getAllCalledFunctions(cc)
|
||||||
|
case SubbyteConstant(cc, _) => getAllCalledFunctions(cc)
|
||||||
|
case CompoundConstant(_, l, r) => getAllCalledFunctions(l) ++ getAllCalledFunctions(r)
|
||||||
|
case MemoryAddressConstant(th) => List(
|
||||||
|
th.name,
|
||||||
|
th.name.stripSuffix(".addr"),
|
||||||
|
th.name.stripSuffix(".hi"),
|
||||||
|
th.name.stripSuffix(".lo"),
|
||||||
|
th.name.stripSuffix(".addr.lo"),
|
||||||
|
th.name.stripSuffix(".addr.hi"))
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAllCalledFunctions(expressions: List[Node]): List[String] = expressions.flatMap {
|
||||||
|
case s: VariableDeclarationStatement => getAllCalledFunctions(s.address.toList) ++ getAllCalledFunctions(s.initialValue.toList)
|
||||||
|
case s: ArrayDeclarationStatement => getAllCalledFunctions(s.address.toList) ++ getAllCalledFunctions(s.elements.getOrElse(Nil))
|
||||||
|
case s: FunctionDeclarationStatement => getAllCalledFunctions(s.address.toList) ++ getAllCalledFunctions(s.statements.getOrElse(Nil))
|
||||||
|
case Assignment(VariableExpression(_), expr) => getAllCalledFunctions(expr :: Nil)
|
||||||
|
case s: Statement => getAllCalledFunctions(s.getAllExpressions)
|
||||||
|
case s: VariableExpression => List(
|
||||||
|
s.name,
|
||||||
|
s.name.stripSuffix(".addr"),
|
||||||
|
s.name.stripSuffix(".hi"),
|
||||||
|
s.name.stripSuffix(".lo"),
|
||||||
|
s.name.stripSuffix(".addr.lo"),
|
||||||
|
s.name.stripSuffix(".addr.hi"))
|
||||||
|
case s: LiteralExpression => Nil
|
||||||
|
case HalfWordExpression(param, _) => getAllCalledFunctions(param :: Nil)
|
||||||
|
case SumExpression(xs, _) => getAllCalledFunctions(xs.map(_._2))
|
||||||
|
case FunctionCallExpression(name, xs) => name :: getAllCalledFunctions(xs)
|
||||||
|
case IndexedExpression(arr, index) => arr :: getAllCalledFunctions(List(index))
|
||||||
|
case SeparateBytesExpression(h, l) => getAllCalledFunctions(List(h, l))
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
104
src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala
Normal file
104
src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package millfork.node.opt
|
||||||
|
|
||||||
|
import millfork.env._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object UnusedGlobalVariables extends NodeOptimization {
|
||||||
|
|
||||||
|
override def optimize(nodes: List[Node]): List[Node] = {
|
||||||
|
|
||||||
|
// TODO: volatile
|
||||||
|
val allNonvolatileGlobalVariables = nodes.flatMap {
|
||||||
|
case v: VariableDeclarationStatement => if (v.address.isDefined) Nil else List(v.name)
|
||||||
|
case v: ArrayDeclarationStatement => if (v.address.isDefined) Nil else List(v.name)
|
||||||
|
case _ => Nil
|
||||||
|
}.toSet
|
||||||
|
val allReadVariables = getAllReadVariables(nodes).toSet
|
||||||
|
val unusedVariables = allNonvolatileGlobalVariables -- allReadVariables
|
||||||
|
if (unusedVariables.nonEmpty) {
|
||||||
|
ErrorReporting.debug("Removing unused global variables: " + unusedVariables.mkString(", "))
|
||||||
|
}
|
||||||
|
removeVariablesFromProgram(nodes, unusedVariables.flatMap(v => Set(v, v + ".hi", v + ".lo")))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def removeVariablesFromProgram(nodes: List[Node], unusedVariables: Set[String]): List[Node] = {
|
||||||
|
nodes match {
|
||||||
|
case (x: ArrayDeclarationStatement) :: xs if unusedVariables(x.name) => removeVariablesFromProgram(xs, unusedVariables)
|
||||||
|
case (x: VariableDeclarationStatement) :: xs if unusedVariables(x.name) => removeVariablesFromProgram(xs, unusedVariables)
|
||||||
|
case (x: FunctionDeclarationStatement) :: xs =>
|
||||||
|
x.copy(statements = x.statements.map(s => removeVariablesFromStatement(s, unusedVariables))) :: removeVariablesFromProgram(xs, unusedVariables)
|
||||||
|
case x :: xs =>
|
||||||
|
x :: removeVariablesFromProgram(xs, unusedVariables)
|
||||||
|
case Nil =>
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAllReadVariables(c: Constant): List[String] = c match {
|
||||||
|
case HalfWordConstant(cc, _) => getAllReadVariables(cc)
|
||||||
|
case SubbyteConstant(cc, _) => getAllReadVariables(cc)
|
||||||
|
case CompoundConstant(_, l, r) => getAllReadVariables(l) ++ getAllReadVariables(r)
|
||||||
|
case MemoryAddressConstant(th) => List(th.name.takeWhile(_ != '.'))
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAllReadVariables(expressions: List[Node]): List[String] = expressions.flatMap {
|
||||||
|
case s: VariableDeclarationStatement => getAllReadVariables(s.address.toList) ++ getAllReadVariables(s.initialValue.toList)
|
||||||
|
case s: ArrayDeclarationStatement => getAllReadVariables(s.address.toList) ++ getAllReadVariables(s.elements.getOrElse(Nil))
|
||||||
|
case s: FunctionDeclarationStatement => getAllReadVariables(s.address.toList) ++ getAllReadVariables(s.statements.getOrElse(Nil))
|
||||||
|
case Assignment(VariableExpression(_), expr) => getAllReadVariables(expr :: Nil)
|
||||||
|
case ExpressionStatement(FunctionCallExpression(op, VariableExpression(_) :: params)) if op.endsWith("=") => getAllReadVariables(params)
|
||||||
|
case s: Statement => getAllReadVariables(s.getAllExpressions)
|
||||||
|
case s: VariableExpression => List(s.name.takeWhile(_ != '.'))
|
||||||
|
case s: LiteralExpression => Nil
|
||||||
|
case HalfWordExpression(param, _) => getAllReadVariables(param :: Nil)
|
||||||
|
case SumExpression(xs, _) => getAllReadVariables(xs.map(_._2))
|
||||||
|
case FunctionCallExpression(name, xs) => name :: getAllReadVariables(xs)
|
||||||
|
case IndexedExpression(arr, index) => arr :: getAllReadVariables(List(index))
|
||||||
|
case SeparateBytesExpression(h, l) => getAllReadVariables(List(h, l))
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
def removeVariablesFromStatement(statements: List[Statement], globalsToRemove: Set[String]): List[Statement] = statements.flatMap {
|
||||||
|
case s: VariableDeclarationStatement =>
|
||||||
|
if (globalsToRemove(s.name)) None else Some(s)
|
||||||
|
case s@ExpressionStatement(FunctionCallExpression(op, VariableExpression(n) :: params)) if op.endsWith("=") =>
|
||||||
|
if (globalsToRemove(n)) params.map(ExpressionStatement) else Some(s)
|
||||||
|
case s@Assignment(VariableExpression(n), expr) =>
|
||||||
|
if (globalsToRemove(n)) Some(ExpressionStatement(expr)) else Some(s)
|
||||||
|
case s@Assignment(SeparateBytesExpression(VariableExpression(h), VariableExpression(l)), expr) =>
|
||||||
|
if (globalsToRemove(h)) {
|
||||||
|
if (globalsToRemove(l))
|
||||||
|
Some(ExpressionStatement(expr))
|
||||||
|
else
|
||||||
|
Some(Assignment(SeparateBytesExpression(BlackHoleExpression, VariableExpression(l)), expr))
|
||||||
|
} else {
|
||||||
|
if (globalsToRemove(l))
|
||||||
|
Some(Assignment(SeparateBytesExpression(VariableExpression(h), BlackHoleExpression), expr))
|
||||||
|
else
|
||||||
|
Some(s)
|
||||||
|
}
|
||||||
|
case s@Assignment(SeparateBytesExpression(h, VariableExpression(l)), expr) =>
|
||||||
|
if (globalsToRemove(l)) Some(Assignment(SeparateBytesExpression(h, BlackHoleExpression), expr))
|
||||||
|
else Some(s)
|
||||||
|
case s@Assignment(SeparateBytesExpression(VariableExpression(h), l), expr) =>
|
||||||
|
if (globalsToRemove(h)) Some(Assignment(SeparateBytesExpression(BlackHoleExpression, l), expr))
|
||||||
|
else Some(s)
|
||||||
|
case s: IfStatement =>
|
||||||
|
Some(s.copy(
|
||||||
|
thenBranch = removeVariablesFromStatement(s.thenBranch, globalsToRemove).asInstanceOf[List[ExecutableStatement]],
|
||||||
|
elseBranch = removeVariablesFromStatement(s.elseBranch, globalsToRemove).asInstanceOf[List[ExecutableStatement]]))
|
||||||
|
case s: WhileStatement =>
|
||||||
|
Some(s.copy(
|
||||||
|
body = removeVariablesFromStatement(s.body, globalsToRemove).asInstanceOf[List[ExecutableStatement]]))
|
||||||
|
case s: DoWhileStatement =>
|
||||||
|
Some(s.copy(
|
||||||
|
body = removeVariablesFromStatement(s.body, globalsToRemove).asInstanceOf[List[ExecutableStatement]]))
|
||||||
|
case s => Some(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
114
src/main/scala/millfork/node/opt/UnusedLocalVariables.scala
Normal file
114
src/main/scala/millfork/node/opt/UnusedLocalVariables.scala
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package millfork.node.opt
|
||||||
|
|
||||||
|
import millfork.assembly.AssemblyLine
|
||||||
|
import millfork.env._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object UnusedLocalVariables extends NodeOptimization {
|
||||||
|
|
||||||
|
override def optimize(nodes: List[Node]): List[Node] = nodes match {
|
||||||
|
case (x: FunctionDeclarationStatement) :: xs =>
|
||||||
|
x.copy(statements = x.statements.map(optimizeVariables)) :: optimize(xs)
|
||||||
|
case x :: xs =>
|
||||||
|
x :: optimize(xs)
|
||||||
|
case Nil =>
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAllLocalVariables(statements: List[Statement]): List[String] = statements.flatMap {
|
||||||
|
case v: VariableDeclarationStatement => List(v.name)
|
||||||
|
case x: IfStatement => getAllLocalVariables(x.thenBranch) ++ getAllLocalVariables(x.elseBranch)
|
||||||
|
case x: WhileStatement => getAllLocalVariables(x.body)
|
||||||
|
case x: DoWhileStatement => getAllLocalVariables(x.body)
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAllReadVariables(c: Constant): List[String] = c match {
|
||||||
|
case HalfWordConstant(cc, _) => getAllReadVariables(cc)
|
||||||
|
case SubbyteConstant(cc, _) => getAllReadVariables(cc)
|
||||||
|
case CompoundConstant(_, l, r) => getAllReadVariables(l) ++ getAllReadVariables(r)
|
||||||
|
case MemoryAddressConstant(th) => List(
|
||||||
|
th.name,
|
||||||
|
th.name.stripSuffix(".addr"),
|
||||||
|
th.name.stripSuffix(".hi"),
|
||||||
|
th.name.stripSuffix(".lo"),
|
||||||
|
th.name.stripSuffix(".addr.lo"),
|
||||||
|
th.name.stripSuffix(".addr.hi"))
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAllReadVariables(expressions: List[Node]): List[String] = expressions.flatMap {
|
||||||
|
case s: VariableExpression => List(
|
||||||
|
s.name,
|
||||||
|
s.name.stripSuffix(".addr"),
|
||||||
|
s.name.stripSuffix(".hi"),
|
||||||
|
s.name.stripSuffix(".lo"),
|
||||||
|
s.name.stripSuffix(".addr.lo"),
|
||||||
|
s.name.stripSuffix(".addr.hi"))
|
||||||
|
case s: LiteralExpression => Nil
|
||||||
|
case HalfWordExpression(param, _) => getAllReadVariables(param :: Nil)
|
||||||
|
case SumExpression(xs, _) => getAllReadVariables(xs.map(_._2))
|
||||||
|
case FunctionCallExpression(_, xs) => getAllReadVariables(xs)
|
||||||
|
case IndexedExpression(arr, index) => arr :: getAllReadVariables(List(index))
|
||||||
|
case SeparateBytesExpression(h, l) => getAllReadVariables(List(h, l))
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def optimizeVariables(statements: List[Statement]): List[Statement] = {
|
||||||
|
val allLocals = getAllLocalVariables(statements)
|
||||||
|
val allRead = getAllReadVariables(statements.flatMap {
|
||||||
|
case Assignment(VariableExpression(_), expression) => List(expression)
|
||||||
|
case ExpressionStatement(FunctionCallExpression(op, VariableExpression(_) :: params)) if op.endsWith("=") => params
|
||||||
|
case x => x.getAllExpressions
|
||||||
|
}).toSet
|
||||||
|
val localsToRemove = allLocals.filterNot(allRead).toSet
|
||||||
|
if (localsToRemove.nonEmpty) {
|
||||||
|
ErrorReporting.debug("Removing unused local variables: " + localsToRemove.mkString(", "))
|
||||||
|
}
|
||||||
|
removeVariables(statements, localsToRemove)
|
||||||
|
}
|
||||||
|
|
||||||
|
def removeVariables(statements: List[Statement], localsToRemove: Set[String]): List[Statement] = statements.flatMap {
|
||||||
|
case s: VariableDeclarationStatement =>
|
||||||
|
if (localsToRemove(s.name)) None else Some(s)
|
||||||
|
case s@ExpressionStatement(FunctionCallExpression(op, VariableExpression(n) :: params)) if op.endsWith("=") =>
|
||||||
|
if (localsToRemove(n)) params.map(ExpressionStatement) else Some(s)
|
||||||
|
case s@Assignment(VariableExpression(n), expr) =>
|
||||||
|
if (localsToRemove(n)) Some(ExpressionStatement(expr)) else Some(s)
|
||||||
|
case s@Assignment(SeparateBytesExpression(VariableExpression(h), VariableExpression(l)), expr) =>
|
||||||
|
if (localsToRemove(h)) {
|
||||||
|
if (localsToRemove(l))
|
||||||
|
Some(ExpressionStatement(expr))
|
||||||
|
else
|
||||||
|
Some(Assignment(SeparateBytesExpression(BlackHoleExpression, VariableExpression(l)), expr))
|
||||||
|
} else {
|
||||||
|
if (localsToRemove(l))
|
||||||
|
Some(Assignment(SeparateBytesExpression(VariableExpression(h), BlackHoleExpression), expr))
|
||||||
|
else
|
||||||
|
Some(s)
|
||||||
|
}
|
||||||
|
case s@Assignment(SeparateBytesExpression(h, VariableExpression(l)), expr) =>
|
||||||
|
if (localsToRemove(l)) Some(Assignment(SeparateBytesExpression(h, BlackHoleExpression), expr))
|
||||||
|
else Some(s)
|
||||||
|
case s@Assignment(SeparateBytesExpression(VariableExpression(h), l), expr) =>
|
||||||
|
if (localsToRemove(h)) Some(Assignment(SeparateBytesExpression(BlackHoleExpression, l), expr))
|
||||||
|
else Some(s)
|
||||||
|
case s: IfStatement =>
|
||||||
|
Some(s.copy(
|
||||||
|
thenBranch = removeVariables(s.thenBranch, localsToRemove).asInstanceOf[List[ExecutableStatement]],
|
||||||
|
elseBranch = removeVariables(s.elseBranch, localsToRemove).asInstanceOf[List[ExecutableStatement]]))
|
||||||
|
case s: WhileStatement =>
|
||||||
|
Some(s.copy(
|
||||||
|
body = removeVariables(s.body, localsToRemove).asInstanceOf[List[ExecutableStatement]]))
|
||||||
|
case s: DoWhileStatement =>
|
||||||
|
Some(s.copy(
|
||||||
|
body = removeVariables(s.body, localsToRemove).asInstanceOf[List[ExecutableStatement]]))
|
||||||
|
case s => Some(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
612
src/main/scala/millfork/output/Assembler.scala
Normal file
612
src/main/scala/millfork/output/Assembler.scala
Normal file
|
@ -0,0 +1,612 @@
|
||||||
|
package millfork.output
|
||||||
|
|
||||||
|
import millfork.assembly.opt.AssemblyOptimization
|
||||||
|
import millfork.assembly.{AddrMode, AssemblyLine, Opcode}
|
||||||
|
import millfork.compiler.{CompilationContext, MlCompiler}
|
||||||
|
import millfork.env._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node.CallGraph
|
||||||
|
import millfork.{CompilationFlag, CompilationOptions}
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
case class AssemblerOutput(code: Array[Byte], asm: Array[String], labels: List[(String, Int)])
|
||||||
|
|
||||||
|
class Assembler(private val rootEnv: Environment) {
|
||||||
|
|
||||||
|
var env = rootEnv.allThings
|
||||||
|
var unoptimizedCodeSize = 0
|
||||||
|
var optimizedCodeSize = 0
|
||||||
|
var initializedArraysSize = 0
|
||||||
|
|
||||||
|
val mem = new CompiledMemory
|
||||||
|
val labelMap = mutable.Map[String, Int]()
|
||||||
|
val bytesToWriteLater = mutable.ListBuffer[(Int, Constant)]()
|
||||||
|
val wordsToWriteLater = mutable.ListBuffer[(Int, Constant)]()
|
||||||
|
|
||||||
|
def writeByte(bank: Int, addr: Int, value: Byte): Unit = {
|
||||||
|
if (mem.banks(bank).occupied(addr)) ErrorReporting.fatal("Overlapping objects")
|
||||||
|
mem.banks(bank).occupied(addr) = true
|
||||||
|
mem.banks(bank).readable(addr) = true
|
||||||
|
mem.banks(bank).output(addr) = value.toByte
|
||||||
|
}
|
||||||
|
|
||||||
|
def writeByte(bank: Int, addr: Int, value: Constant): Unit = {
|
||||||
|
if (mem.banks(bank).occupied(addr)) ErrorReporting.fatal("Overlapping objects")
|
||||||
|
mem.banks(bank).occupied(addr) = true
|
||||||
|
mem.banks(bank).readable(addr) = true
|
||||||
|
value match {
|
||||||
|
case NumericConstant(x, _) =>
|
||||||
|
if (x > 0xffff) ErrorReporting.error("Byte overflow")
|
||||||
|
mem.banks(0).output(addr) = x.toByte
|
||||||
|
case _ =>
|
||||||
|
bytesToWriteLater += addr -> value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def writeWord(bank: Int, addr: Int, value: Constant): Unit = {
|
||||||
|
if (mem.banks(bank).occupied(addr)) ErrorReporting.fatal("Overlapping objects")
|
||||||
|
mem.banks(bank).occupied(addr) = true
|
||||||
|
mem.banks(bank).occupied(addr + 1) = true
|
||||||
|
mem.banks(bank).readable(addr) = true
|
||||||
|
mem.banks(bank).readable(addr + 1) = true
|
||||||
|
value match {
|
||||||
|
case NumericConstant(x, _) =>
|
||||||
|
if (x > 0xffff) ErrorReporting.error("Word overflow")
|
||||||
|
mem.banks(bank).output(addr) = x.toByte
|
||||||
|
mem.banks(bank).output(addr + 1) = (x >> 8).toByte
|
||||||
|
case _ =>
|
||||||
|
wordsToWriteLater += addr -> value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def deepConstResolve(c: Constant): Long = {
|
||||||
|
c match {
|
||||||
|
case NumericConstant(v, _) => v
|
||||||
|
case MemoryAddressConstant(th) =>
|
||||||
|
if (labelMap.contains(th.name)) return labelMap(th.name)
|
||||||
|
if (labelMap.contains(th.name + "`")) return labelMap(th.name)
|
||||||
|
if (labelMap.contains(th.name + ".addr")) return labelMap(th.name)
|
||||||
|
val x1 = env.maybeGet[ConstantThing](th.name).map(_.value)
|
||||||
|
val x2 = env.maybeGet[ConstantThing](th.name + "`").map(_.value)
|
||||||
|
val x3 = env.maybeGet[NormalFunction](th.name).flatMap(_.address)
|
||||||
|
val x4 = env.maybeGet[ConstantThing](th.name + ".addr").map(_.value)
|
||||||
|
val x5 = env.maybeGet[RelativeVariable](th.name).map(_.address)
|
||||||
|
val x6 = env.maybeGet[ConstantThing](th.name.stripSuffix(".array") + ".addr").map(_.value)
|
||||||
|
val x = x1.orElse(x2).orElse(x3).orElse(x4).orElse(x5).orElse(x6)
|
||||||
|
x match {
|
||||||
|
case Some(cc) =>
|
||||||
|
deepConstResolve(cc)
|
||||||
|
case None =>
|
||||||
|
println(th)
|
||||||
|
???
|
||||||
|
}
|
||||||
|
case HalfWordConstant(cc, true) => deepConstResolve(cc).>>>(8).&(0xff)
|
||||||
|
case HalfWordConstant(cc, false) => deepConstResolve(cc).&(0xff)
|
||||||
|
case SubbyteConstant(cc, i) => deepConstResolve(cc).>>>(i * 8).&(0xff)
|
||||||
|
case CompoundConstant(operator, lc, rc) =>
|
||||||
|
val l = deepConstResolve(lc)
|
||||||
|
val r = deepConstResolve(rc)
|
||||||
|
operator match {
|
||||||
|
case MathOperator.Plus => l + r
|
||||||
|
case MathOperator.Minus => l - r
|
||||||
|
case MathOperator.Times => l * r
|
||||||
|
case MathOperator.Shl => l << r
|
||||||
|
case MathOperator.Shr => l >>> r
|
||||||
|
case MathOperator.DecimalPlus => asDecimal(l, r, _ + _)
|
||||||
|
case MathOperator.DecimalMinus => asDecimal(l, r, _ - _)
|
||||||
|
case MathOperator.DecimalTimes => asDecimal(l, r, _ * _)
|
||||||
|
case MathOperator.DecimalShl => asDecimal(l, 1 << r, _ * _)
|
||||||
|
case MathOperator.DecimalShr => asDecimal(l, 1 << r, _ / _)
|
||||||
|
case MathOperator.And => l & r
|
||||||
|
case MathOperator.Exor => l ^ r
|
||||||
|
case MathOperator.Or => l | r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def parseNormalToDecimalValue(a: Long): Long = {
|
||||||
|
if (a < 0) -parseNormalToDecimalValue(-a)
|
||||||
|
var x = a
|
||||||
|
var result = 0L
|
||||||
|
var multiplier = 1L
|
||||||
|
while (x > 0) {
|
||||||
|
result += multiplier * (a % 16L)
|
||||||
|
x /= 16L
|
||||||
|
multiplier *= 10L
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
private def storeDecimalValueInNormalRespresentation(a: Long): Long = {
|
||||||
|
if (a < 0) -storeDecimalValueInNormalRespresentation(-a)
|
||||||
|
var x = a
|
||||||
|
var result = 0L
|
||||||
|
var multiplier = 1L
|
||||||
|
while (x > 0) {
|
||||||
|
result += multiplier * (a % 10L)
|
||||||
|
x /= 10L
|
||||||
|
multiplier *= 16L
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
private def asDecimal(a: Long, b: Long, f: (Long, Long) => Long): Long =
|
||||||
|
storeDecimalValueInNormalRespresentation(f(parseNormalToDecimalValue(a), parseNormalToDecimalValue(b)))
|
||||||
|
|
||||||
|
def assemble(callGraph: CallGraph, optimizations: Seq[AssemblyOptimization], options: CompilationOptions): AssemblerOutput = {
|
||||||
|
val platform = options.platform
|
||||||
|
|
||||||
|
val assembly = mutable.ArrayBuffer[String]()
|
||||||
|
|
||||||
|
env.allPreallocatables.foreach {
|
||||||
|
case InitializedArray(name, Some(NumericConstant(address, _)), items) =>
|
||||||
|
var index = address.toInt
|
||||||
|
assembly.append("* = $" + index.toHexString)
|
||||||
|
assembly.append(name)
|
||||||
|
for (item <- items) {
|
||||||
|
writeByte(0, index, item)
|
||||||
|
assembly.append(" !byte " + item)
|
||||||
|
mem.banks(0).writeable(index) = true
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
initializedArraysSize += items.length
|
||||||
|
case InitializedArray(name, Some(_), items) => ???
|
||||||
|
case f: NormalFunction if f.address.isDefined =>
|
||||||
|
var index = f.address.get.asInstanceOf[NumericConstant].value.toInt
|
||||||
|
labelMap(f.name) = index
|
||||||
|
compileFunction(f, index, optimizations, assembly, options)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = platform.org
|
||||||
|
env.allPreallocatables.foreach {
|
||||||
|
case f: NormalFunction if f.address.isEmpty && f.name == "main" =>
|
||||||
|
labelMap(f.name) = index
|
||||||
|
index = compileFunction(f, index, optimizations, assembly, options)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
env.allPreallocatables.foreach {
|
||||||
|
case f: NormalFunction if f.address.isEmpty && f.name != "main" =>
|
||||||
|
labelMap(f.name) = index
|
||||||
|
index = compileFunction(f, index, optimizations, assembly, options)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
env.allPreallocatables.foreach {
|
||||||
|
case InitializedArray(name, None, items) =>
|
||||||
|
labelMap(name) = index
|
||||||
|
assembly.append("* = $" + index.toHexString)
|
||||||
|
assembly.append(name)
|
||||||
|
for (item <- items) {
|
||||||
|
writeByte(0, index, item)
|
||||||
|
assembly.append(" !byte " + item)
|
||||||
|
mem.banks(0).writeable(index) = true
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
initializedArraysSize += items.length
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
val allocator = platform.allocator
|
||||||
|
allocator.notifyAboutEndOfCode(index)
|
||||||
|
allocator.onEachByte = { addr =>
|
||||||
|
mem.banks(0).readable(addr) = true
|
||||||
|
mem.banks(0).writeable(addr) = true
|
||||||
|
}
|
||||||
|
env.allocateVariables(None, callGraph, allocator, options, labelMap.put)
|
||||||
|
|
||||||
|
env = rootEnv.allThings
|
||||||
|
|
||||||
|
for ((addr, b) <- bytesToWriteLater) {
|
||||||
|
val value = deepConstResolve(b)
|
||||||
|
mem.banks(0).output(addr) = value.toByte
|
||||||
|
}
|
||||||
|
for ((addr, b) <- wordsToWriteLater) {
|
||||||
|
val value = deepConstResolve(b)
|
||||||
|
mem.banks(0).output(addr) = value.toByte
|
||||||
|
mem.banks(0).output(addr + 1) = value.>>>(8).toByte
|
||||||
|
}
|
||||||
|
|
||||||
|
val start = mem.banks(0).occupied.indexOf(true)
|
||||||
|
val end = mem.banks(0).occupied.lastIndexOf(true)
|
||||||
|
val length = end - start + 1
|
||||||
|
mem.banks(0).start = start
|
||||||
|
mem.banks(0).end = end
|
||||||
|
|
||||||
|
labelMap.toList.sorted.foreach {case (l, v) =>
|
||||||
|
assembly += f"$l%-30s = $$$v%04X"
|
||||||
|
}
|
||||||
|
labelMap.toList.sortBy{case (a,b) => b->a}.foreach {case (l, v) =>
|
||||||
|
assembly += f" ; $$$v%04X = $l%s"
|
||||||
|
}
|
||||||
|
|
||||||
|
AssemblerOutput(platform.outputPackager.packageOutput(mem, 0), assembly.toArray, labelMap.toList)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def compileFunction(f: NormalFunction, startFrom: Int, optimizations: Seq[AssemblyOptimization], assOut: mutable.ArrayBuffer[String], options: CompilationOptions): Int = {
|
||||||
|
ErrorReporting.debug("Compiling: " + f.name, f.position)
|
||||||
|
var index = startFrom
|
||||||
|
assOut.append("* = $" + startFrom.toHexString)
|
||||||
|
val unoptimized = MlCompiler.compile(CompilationContext(env = f.environment, function = f, extraStackOffset = 0, options = options)).linearize
|
||||||
|
unoptimizedCodeSize += unoptimized.map(_.sizeInBytes).sum
|
||||||
|
val code = optimizations.foldLeft(unoptimized) { (c, opt) =>
|
||||||
|
opt.optimize(f, c, options)
|
||||||
|
}
|
||||||
|
optimizedCodeSize += code.map(_.sizeInBytes).sum
|
||||||
|
import millfork.assembly.AddrMode._
|
||||||
|
import millfork.assembly.Opcode._
|
||||||
|
for (instr <- code) {
|
||||||
|
if (instr.isPrintable) {
|
||||||
|
assOut.append(instr.toString)
|
||||||
|
}
|
||||||
|
instr match {
|
||||||
|
case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(labelName)), _) =>
|
||||||
|
labelMap(labelName) = index
|
||||||
|
case AssemblyLine(_, DoesNotExist, _, _) =>
|
||||||
|
()
|
||||||
|
case AssemblyLine(op, Implied, _, _) =>
|
||||||
|
writeByte(0, index, Assembler.opcodeFor(op, Implied, options))
|
||||||
|
index += 1
|
||||||
|
case AssemblyLine(op, Relative, param, _) =>
|
||||||
|
writeByte(0, index, Assembler.opcodeFor(op, Relative, options))
|
||||||
|
writeByte(0, index + 1, param - (index + 2))
|
||||||
|
index += 2
|
||||||
|
case AssemblyLine(op, am@(Immediate | ZeroPage | ZeroPageX | ZeroPageY | IndexedY | IndexedX | ZeroPageIndirect), param, _) =>
|
||||||
|
writeByte(0, index, Assembler.opcodeFor(op, am, options))
|
||||||
|
writeByte(0, index + 1, param)
|
||||||
|
index += 2
|
||||||
|
case AssemblyLine(op, am@(Absolute | AbsoluteY | AbsoluteX | Indirect | AbsoluteIndexedX), param, _) =>
|
||||||
|
writeByte(0, index, Assembler.opcodeFor(op, am, options))
|
||||||
|
writeWord(0, index + 1, param)
|
||||||
|
index += 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Assembler {
|
||||||
|
val opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
|
||||||
|
val illegalOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
|
||||||
|
val cmosOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
|
||||||
|
|
||||||
|
def opcodeFor(opcode: Opcode.Value, addrMode: AddrMode.Value, options: CompilationOptions): Byte = {
|
||||||
|
val key = opcode -> addrMode
|
||||||
|
opcodes.get(key) match {
|
||||||
|
case Some(v) => v
|
||||||
|
case None =>
|
||||||
|
illegalOpcodes.get(key) match {
|
||||||
|
case Some(v) =>
|
||||||
|
if (options.flag(CompilationFlag.EmitIllegals)) v
|
||||||
|
else ErrorReporting.fatal("Cannot assemble an illegal opcode " + key)
|
||||||
|
case None =>
|
||||||
|
cmosOpcodes.get(key) match {
|
||||||
|
case Some(v) =>
|
||||||
|
if (options.flag(CompilationFlag.EmitCmosOpcodes)) v
|
||||||
|
else ErrorReporting.fatal("Cannot assemble a CMOS opcode " + key)
|
||||||
|
case None =>
|
||||||
|
ErrorReporting.fatal("Cannot assemble an unknown opcode " + key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def op(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
|
||||||
|
if (x < 0 || x > 0xff) ???
|
||||||
|
opcodes(op -> am) = x.toByte
|
||||||
|
if (am == AddrMode.Relative) opcodes(op -> AddrMode.Immediate) = x.toByte
|
||||||
|
}
|
||||||
|
|
||||||
|
private def cm(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
|
||||||
|
if (x < 0 || x > 0xff) ???
|
||||||
|
cmosOpcodes(op -> am) = x.toByte
|
||||||
|
}
|
||||||
|
|
||||||
|
private def il(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
|
||||||
|
if (x < 0 || x > 0xff) ???
|
||||||
|
illegalOpcodes(op -> am) = x.toByte
|
||||||
|
}
|
||||||
|
|
||||||
|
def getStandardLegalOpcodes: Set[Int] = opcodes.values.map(_ & 0xff).toSet
|
||||||
|
|
||||||
|
import AddrMode._
|
||||||
|
import Opcode._
|
||||||
|
|
||||||
|
op(ADC, Immediate, 0x69)
|
||||||
|
op(ADC, ZeroPage, 0x65)
|
||||||
|
op(ADC, ZeroPageX, 0x75)
|
||||||
|
op(ADC, Absolute, 0x6D)
|
||||||
|
op(ADC, AbsoluteX, 0x7D)
|
||||||
|
op(ADC, AbsoluteY, 0x79)
|
||||||
|
op(ADC, IndexedX, 0x61)
|
||||||
|
op(ADC, IndexedY, 0x71)
|
||||||
|
|
||||||
|
op(AND, Immediate, 0x29)
|
||||||
|
op(AND, ZeroPage, 0x25)
|
||||||
|
op(AND, ZeroPageX, 0x35)
|
||||||
|
op(AND, Absolute, 0x2D)
|
||||||
|
op(AND, AbsoluteX, 0x3D)
|
||||||
|
op(AND, AbsoluteY, 0x39)
|
||||||
|
op(AND, IndexedX, 0x21)
|
||||||
|
op(AND, IndexedY, 0x31)
|
||||||
|
|
||||||
|
op(ASL, Implied, 0x0A)
|
||||||
|
op(ASL, ZeroPage, 0x06)
|
||||||
|
op(ASL, ZeroPageX, 0x16)
|
||||||
|
op(ASL, Absolute, 0x0E)
|
||||||
|
op(ASL, AbsoluteX, 0x1E)
|
||||||
|
|
||||||
|
op(BIT, ZeroPage, 0x24)
|
||||||
|
op(BIT, Absolute, 0x2C)
|
||||||
|
|
||||||
|
op(BPL, Relative, 0x10)
|
||||||
|
op(BMI, Relative, 0x30)
|
||||||
|
op(BVC, Relative, 0x50)
|
||||||
|
op(BVS, Relative, 0x70)
|
||||||
|
op(BCC, Relative, 0x90)
|
||||||
|
op(BCS, Relative, 0xB0)
|
||||||
|
op(BNE, Relative, 0xD0)
|
||||||
|
op(BEQ, Relative, 0xF0)
|
||||||
|
|
||||||
|
op(BRK, Implied, 0)
|
||||||
|
|
||||||
|
op(CMP, Immediate, 0xC9)
|
||||||
|
op(CMP, ZeroPage, 0xC5)
|
||||||
|
op(CMP, ZeroPageX, 0xD5)
|
||||||
|
op(CMP, Absolute, 0xCD)
|
||||||
|
op(CMP, AbsoluteX, 0xDD)
|
||||||
|
op(CMP, AbsoluteY, 0xD9)
|
||||||
|
op(CMP, IndexedX, 0xC1)
|
||||||
|
op(CMP, IndexedY, 0xD1)
|
||||||
|
|
||||||
|
op(CPX, Immediate, 0xE0)
|
||||||
|
op(CPX, ZeroPage, 0xE4)
|
||||||
|
op(CPX, Absolute, 0xEC)
|
||||||
|
|
||||||
|
op(CPY, Immediate, 0xC0)
|
||||||
|
op(CPY, ZeroPage, 0xC4)
|
||||||
|
op(CPY, Absolute, 0xCC)
|
||||||
|
|
||||||
|
op(DEC, ZeroPage, 0xC6)
|
||||||
|
op(DEC, ZeroPageX, 0xD6)
|
||||||
|
op(DEC, Absolute, 0xCE)
|
||||||
|
op(DEC, AbsoluteX, 0xDE)
|
||||||
|
|
||||||
|
op(EOR, Immediate, 0x49)
|
||||||
|
op(EOR, ZeroPage, 0x45)
|
||||||
|
op(EOR, ZeroPageX, 0x55)
|
||||||
|
op(EOR, Absolute, 0x4D)
|
||||||
|
op(EOR, AbsoluteX, 0x5D)
|
||||||
|
op(EOR, AbsoluteY, 0x59)
|
||||||
|
op(EOR, IndexedX, 0x41)
|
||||||
|
op(EOR, IndexedY, 0x51)
|
||||||
|
|
||||||
|
op(INC, ZeroPage, 0xE6)
|
||||||
|
op(INC, ZeroPageX, 0xF6)
|
||||||
|
op(INC, Absolute, 0xEE)
|
||||||
|
op(INC, AbsoluteX, 0xFE)
|
||||||
|
|
||||||
|
op(CLC, Implied, 0x18)
|
||||||
|
op(SEC, Implied, 0x38)
|
||||||
|
op(CLI, Implied, 0x58)
|
||||||
|
op(SEI, Implied, 0x78)
|
||||||
|
op(CLV, Implied, 0xB8)
|
||||||
|
op(CLD, Implied, 0xD8)
|
||||||
|
op(SED, Implied, 0xF8)
|
||||||
|
|
||||||
|
op(JMP, Absolute, 0x4C)
|
||||||
|
op(JMP, Indirect, 0x6C)
|
||||||
|
|
||||||
|
op(JSR, Absolute, 0x20)
|
||||||
|
|
||||||
|
op(LDA, Immediate, 0xA9)
|
||||||
|
op(LDA, ZeroPage, 0xA5)
|
||||||
|
op(LDA, ZeroPageX, 0xB5)
|
||||||
|
op(LDA, Absolute, 0xAD)
|
||||||
|
op(LDA, AbsoluteX, 0xBD)
|
||||||
|
op(LDA, AbsoluteY, 0xB9)
|
||||||
|
op(LDA, IndexedX, 0xA1)
|
||||||
|
op(LDA, IndexedY, 0xB1)
|
||||||
|
|
||||||
|
op(LDX, Immediate, 0xA2)
|
||||||
|
op(LDX, ZeroPage, 0xA6)
|
||||||
|
op(LDX, ZeroPageY, 0xB6)
|
||||||
|
op(LDX, Absolute, 0xAE)
|
||||||
|
op(LDX, AbsoluteY, 0xBE)
|
||||||
|
|
||||||
|
op(LDY, Immediate, 0xA0)
|
||||||
|
op(LDY, ZeroPage, 0xA4)
|
||||||
|
op(LDY, ZeroPageX, 0xB4)
|
||||||
|
op(LDY, Absolute, 0xAC)
|
||||||
|
op(LDY, AbsoluteX, 0xBC)
|
||||||
|
|
||||||
|
op(LSR, Implied, 0x4A)
|
||||||
|
op(LSR, ZeroPage, 0x46)
|
||||||
|
op(LSR, ZeroPageX, 0x56)
|
||||||
|
op(LSR, Absolute, 0x4E)
|
||||||
|
op(LSR, AbsoluteX, 0x5E)
|
||||||
|
|
||||||
|
op(NOP, Implied, 0xEA)
|
||||||
|
|
||||||
|
op(ORA, Immediate, 0x09)
|
||||||
|
op(ORA, ZeroPage, 0x05)
|
||||||
|
op(ORA, ZeroPageX, 0x15)
|
||||||
|
op(ORA, Absolute, 0x0D)
|
||||||
|
op(ORA, AbsoluteX, 0x1D)
|
||||||
|
op(ORA, AbsoluteY, 0x19)
|
||||||
|
op(ORA, IndexedX, 0x01)
|
||||||
|
op(ORA, IndexedY, 0x11)
|
||||||
|
|
||||||
|
op(TAX, Implied, 0xAA)
|
||||||
|
op(TXA, Implied, 0x8A)
|
||||||
|
op(DEX, Implied, 0xCA)
|
||||||
|
op(INX, Implied, 0xE8)
|
||||||
|
op(TAY, Implied, 0xA8)
|
||||||
|
op(TYA, Implied, 0x98)
|
||||||
|
op(DEY, Implied, 0x88)
|
||||||
|
op(INY, Implied, 0xC8)
|
||||||
|
|
||||||
|
op(ROL, Implied, 0x2A)
|
||||||
|
op(ROL, ZeroPage, 0x26)
|
||||||
|
op(ROL, ZeroPageX, 0x36)
|
||||||
|
op(ROL, Absolute, 0x2E)
|
||||||
|
op(ROL, AbsoluteX, 0x3E)
|
||||||
|
|
||||||
|
op(ROR, Implied, 0x6A)
|
||||||
|
op(ROR, ZeroPage, 0x66)
|
||||||
|
op(ROR, ZeroPageX, 0x76)
|
||||||
|
op(ROR, Absolute, 0x6E)
|
||||||
|
op(ROR, AbsoluteX, 0x7E)
|
||||||
|
|
||||||
|
op(RTI, Implied, 0x40)
|
||||||
|
op(RTS, Implied, 0x60)
|
||||||
|
|
||||||
|
op(SBC, Immediate, 0xE9)
|
||||||
|
op(SBC, ZeroPage, 0xE5)
|
||||||
|
op(SBC, ZeroPageX, 0xF5)
|
||||||
|
op(SBC, Absolute, 0xED)
|
||||||
|
op(SBC, AbsoluteX, 0xFD)
|
||||||
|
op(SBC, AbsoluteY, 0xF9)
|
||||||
|
op(SBC, IndexedX, 0xE1)
|
||||||
|
op(SBC, IndexedY, 0xF1)
|
||||||
|
|
||||||
|
op(STA, ZeroPage, 0x85)
|
||||||
|
op(STA, ZeroPageX, 0x95)
|
||||||
|
op(STA, Absolute, 0x8D)
|
||||||
|
op(STA, AbsoluteX, 0x9D)
|
||||||
|
op(STA, AbsoluteY, 0x99)
|
||||||
|
op(STA, IndexedX, 0x81)
|
||||||
|
op(STA, IndexedY, 0x91)
|
||||||
|
|
||||||
|
op(TXS, Implied, 0x9A)
|
||||||
|
op(TSX, Implied, 0xBA)
|
||||||
|
op(PHA, Implied, 0x48)
|
||||||
|
op(PLA, Implied, 0x68)
|
||||||
|
op(PHP, Implied, 0x08)
|
||||||
|
op(PLP, Implied, 0x28)
|
||||||
|
|
||||||
|
op(STX, ZeroPage, 0x86)
|
||||||
|
op(STX, ZeroPageY, 0x96)
|
||||||
|
op(STX, Absolute, 0x8E)
|
||||||
|
|
||||||
|
op(STY, ZeroPage, 0x84)
|
||||||
|
op(STY, ZeroPageX, 0x94)
|
||||||
|
op(STY, Absolute, 0x8C)
|
||||||
|
|
||||||
|
il(LAX, ZeroPage, 0xA7)
|
||||||
|
il(LAX, ZeroPageY, 0xB7)
|
||||||
|
il(LAX, Absolute, 0xAF)
|
||||||
|
il(LAX, AbsoluteY, 0xBF)
|
||||||
|
il(LAX, IndexedX, 0xA3)
|
||||||
|
il(LAX, IndexedY, 0xB3)
|
||||||
|
|
||||||
|
il(SAX, ZeroPage, 0x87)
|
||||||
|
il(SAX, ZeroPageY, 0x97)
|
||||||
|
il(SAX, Absolute, 0x8F)
|
||||||
|
il(TAS, AbsoluteY, 0x9B)
|
||||||
|
il(AHX, AbsoluteY, 0x9F)
|
||||||
|
il(SAX, IndexedX, 0x83)
|
||||||
|
il(AHX, IndexedY, 0x93)
|
||||||
|
|
||||||
|
il(ANC, Immediate, 0x0B)
|
||||||
|
il(ALR, Immediate, 0x4B)
|
||||||
|
il(ARR, Immediate, 0x6B)
|
||||||
|
il(XAA, Immediate, 0x8B)
|
||||||
|
il(LXA, Immediate, 0xAB)
|
||||||
|
il(SBX, Immediate, 0xCB)
|
||||||
|
|
||||||
|
il(SLO, ZeroPage, 0x07)
|
||||||
|
il(SLO, ZeroPageX, 0x17)
|
||||||
|
il(SLO, IndexedX, 0x03)
|
||||||
|
il(SLO, IndexedY, 0x13)
|
||||||
|
il(SLO, Absolute, 0x0F)
|
||||||
|
il(SLO, AbsoluteX, 0x1F)
|
||||||
|
il(SLO, AbsoluteY, 0x1B)
|
||||||
|
|
||||||
|
il(RLA, ZeroPage, 0x27)
|
||||||
|
il(RLA, ZeroPageX, 0x37)
|
||||||
|
il(RLA, IndexedX, 0x23)
|
||||||
|
il(RLA, IndexedY, 0x33)
|
||||||
|
il(RLA, Absolute, 0x2F)
|
||||||
|
il(RLA, AbsoluteX, 0x3F)
|
||||||
|
il(RLA, AbsoluteY, 0x3B)
|
||||||
|
|
||||||
|
il(SRE, ZeroPage, 0x47)
|
||||||
|
il(SRE, ZeroPageX, 0x57)
|
||||||
|
il(SRE, IndexedX, 0x43)
|
||||||
|
il(SRE, IndexedY, 0x53)
|
||||||
|
il(SRE, Absolute, 0x4F)
|
||||||
|
il(SRE, AbsoluteX, 0x5F)
|
||||||
|
il(SRE, AbsoluteY, 0x5B)
|
||||||
|
|
||||||
|
il(RRA, ZeroPage, 0x67)
|
||||||
|
il(RRA, ZeroPageX, 0x77)
|
||||||
|
il(RRA, IndexedX, 0x63)
|
||||||
|
il(RRA, IndexedY, 0x73)
|
||||||
|
il(RRA, Absolute, 0x6F)
|
||||||
|
il(RRA, AbsoluteX, 0x7F)
|
||||||
|
il(RRA, AbsoluteY, 0x7B)
|
||||||
|
|
||||||
|
il(DCP, ZeroPage, 0xC7)
|
||||||
|
il(DCP, ZeroPageX, 0xD7)
|
||||||
|
il(DCP, IndexedX, 0xC3)
|
||||||
|
il(DCP, IndexedY, 0xD3)
|
||||||
|
il(DCP, Absolute, 0xCF)
|
||||||
|
il(DCP, AbsoluteX, 0xDF)
|
||||||
|
il(DCP, AbsoluteY, 0xDB)
|
||||||
|
|
||||||
|
il(ISC, ZeroPage, 0xE7)
|
||||||
|
il(ISC, ZeroPageX, 0xF7)
|
||||||
|
il(ISC, IndexedX, 0xE3)
|
||||||
|
il(ISC, IndexedY, 0xF3)
|
||||||
|
il(ISC, Absolute, 0xEF)
|
||||||
|
il(ISC, AbsoluteX, 0xFF)
|
||||||
|
il(ISC, AbsoluteY, 0xFB)
|
||||||
|
|
||||||
|
il(NOP, Immediate, 0x80)
|
||||||
|
il(NOP, ZeroPage, 0x44)
|
||||||
|
il(NOP, ZeroPageX, 0x54)
|
||||||
|
il(NOP, Absolute, 0x5C)
|
||||||
|
il(NOP, AbsoluteX, 0x1C)
|
||||||
|
|
||||||
|
cm(NOP, Immediate, 0x02)
|
||||||
|
cm(NOP, ZeroPage, 0x44)
|
||||||
|
cm(NOP, ZeroPageX, 0x54)
|
||||||
|
cm(NOP, Absolute, 0x5C)
|
||||||
|
|
||||||
|
cm(STZ, ZeroPage, 0x64)
|
||||||
|
cm(STZ, ZeroPageX, 0x74)
|
||||||
|
cm(STZ, Absolute, 0x9C)
|
||||||
|
cm(STZ, AbsoluteX, 0x9E)
|
||||||
|
|
||||||
|
cm(PHX, Implied, 0xDA)
|
||||||
|
cm(PHY, Implied, 0x5A)
|
||||||
|
cm(PLX, Implied, 0xFA)
|
||||||
|
cm(PLY, Implied, 0x7A)
|
||||||
|
|
||||||
|
cm(ORA, ZeroPageIndirect, 0x12)
|
||||||
|
cm(AND, ZeroPageIndirect, 0x32)
|
||||||
|
cm(EOR, ZeroPageIndirect, 0x52)
|
||||||
|
cm(ADC, ZeroPageIndirect, 0x72)
|
||||||
|
cm(STA, ZeroPageIndirect, 0x92)
|
||||||
|
cm(LDA, ZeroPageIndirect, 0xB2)
|
||||||
|
cm(CMP, ZeroPageIndirect, 0xD2)
|
||||||
|
cm(SBC, ZeroPageIndirect, 0xF2)
|
||||||
|
|
||||||
|
cm(TSB, ZeroPage, 0x04)
|
||||||
|
cm(TSB, Absolute, 0x0C)
|
||||||
|
cm(TRB, ZeroPage, 0x14)
|
||||||
|
cm(TRB, Absolute, 0x1C)
|
||||||
|
|
||||||
|
cm(BIT, ZeroPageX, 0x34)
|
||||||
|
cm(BIT, AbsoluteX, 0x3C)
|
||||||
|
cm(INC, Implied, 0x1A)
|
||||||
|
cm(DEC, Implied, 0x3A)
|
||||||
|
cm(JMP, AbsoluteIndexedX, 0x7C)
|
||||||
|
cm(WAI, Implied, 0xCB)
|
||||||
|
cm(STP, Implied, 0xDB)
|
||||||
|
|
||||||
|
}
|
29
src/main/scala/millfork/output/CompiledMemory.scala
Normal file
29
src/main/scala/millfork/output/CompiledMemory.scala
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package millfork.output
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
class CompiledMemory {
|
||||||
|
val banks = mutable.Map(0 -> new MemoryBank)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MemoryBank {
|
||||||
|
def readByte(addr: Int) = output(addr) & 0xff
|
||||||
|
|
||||||
|
def readWord(addr: Int) = readByte(addr) + (readByte(addr + 1) << 8)
|
||||||
|
|
||||||
|
def readMedium(addr: Int) = readByte(addr) + (readByte(addr + 1) << 8) + (readByte(addr + 2) << 16)
|
||||||
|
|
||||||
|
def readLong(addr: Int) = readByte(addr) + (readByte(addr + 1) << 8) + (readByte(addr + 2) << 16) + (readByte(addr + 3) << 24)
|
||||||
|
|
||||||
|
def readWord(addrHi: Int, addrLo: Int) = readByte(addrLo) + (readByte(addrHi) << 8)
|
||||||
|
|
||||||
|
val output = Array.fill[Byte](1 << 16)(0)
|
||||||
|
val occupied = Array.fill(1 << 16)(false)
|
||||||
|
val readable = Array.fill(1 << 16)(false)
|
||||||
|
val writeable = Array.fill(1 << 16)(false)
|
||||||
|
var start: Int = 0
|
||||||
|
var end: Int = 0
|
||||||
|
}
|
60
src/main/scala/millfork/output/OutputPackager.scala
Normal file
60
src/main/scala/millfork/output/OutputPackager.scala
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package millfork.output
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
trait OutputPackager {
|
||||||
|
def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte]
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SequenceOutput(children: List[OutputPackager]) extends OutputPackager {
|
||||||
|
def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = {
|
||||||
|
val baos = new ByteArrayOutputStream
|
||||||
|
children.foreach { c =>
|
||||||
|
val a = c.packageOutput(mem, bank)
|
||||||
|
baos.write(a, 0, a.length)
|
||||||
|
}
|
||||||
|
baos.toByteArray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class ConstOutput(byte: Byte) extends OutputPackager {
|
||||||
|
def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = Array(byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class CurrentBankFragmentOutput(start: Int, end: Int) extends OutputPackager {
|
||||||
|
def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = {
|
||||||
|
val b = mem.banks(bank)
|
||||||
|
b.output.slice(start, end + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class BankFragmentOutput(alwaysBank: Int, start: Int, end: Int) extends OutputPackager {
|
||||||
|
def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = {
|
||||||
|
val b = mem.banks(alwaysBank)
|
||||||
|
b.output.slice(start, end + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object StartAddressOutput extends OutputPackager {
|
||||||
|
def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = {
|
||||||
|
val b = mem.banks(bank)
|
||||||
|
Array(b.start.toByte, b.start.>>(8).toByte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object EndAddressOutput extends OutputPackager {
|
||||||
|
def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = {
|
||||||
|
val b = mem.banks(bank)
|
||||||
|
Array(b.end.toByte, b.end.>>(8).toByte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object AllocatedDataOutput extends OutputPackager {
|
||||||
|
def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = {
|
||||||
|
val b = mem.banks(bank)
|
||||||
|
b.output.slice(b.start, b.end + 1)
|
||||||
|
}
|
||||||
|
}
|
96
src/main/scala/millfork/output/VariableAllocator.scala
Normal file
96
src/main/scala/millfork/output/VariableAllocator.scala
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package millfork.output
|
||||||
|
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node.{CallGraph, VariableVertex}
|
||||||
|
import millfork.{CompilationFlag, CompilationOptions}
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
|
||||||
|
sealed trait ByteAllocator {
|
||||||
|
def notifyAboutEndOfCode(org: Int): Unit
|
||||||
|
|
||||||
|
def allocateBytes(count: Int, options: CompilationOptions): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpwardByteAllocator(startAt: Int, endBefore: Int) extends ByteAllocator {
|
||||||
|
private var nextByte = startAt
|
||||||
|
|
||||||
|
def allocateBytes(count: Int, options: CompilationOptions): Int = {
|
||||||
|
if (count == 2 && (nextByte & 0xff) == 0xff && options.flag(CompilationFlag.PreventJmpIndirectBug)) nextByte += 1
|
||||||
|
val t = nextByte
|
||||||
|
nextByte += count
|
||||||
|
if (nextByte > endBefore) {
|
||||||
|
ErrorReporting.fatal("Out of high memory")
|
||||||
|
}
|
||||||
|
t
|
||||||
|
}
|
||||||
|
|
||||||
|
def notifyAboutEndOfCode(org: Int): Unit = ()
|
||||||
|
}
|
||||||
|
|
||||||
|
class AfterCodeByteAllocator(endBefore: Int) extends ByteAllocator {
|
||||||
|
var nextByte = 0x200
|
||||||
|
|
||||||
|
def allocateBytes(count: Int, options: CompilationOptions): Int = {
|
||||||
|
if (count == 2 && (nextByte & 0xff) == 0xff && options.flag(CompilationFlag.PreventJmpIndirectBug)) nextByte += 1
|
||||||
|
val t = nextByte
|
||||||
|
nextByte += count
|
||||||
|
if (nextByte > endBefore) {
|
||||||
|
ErrorReporting.fatal("Out of high memory")
|
||||||
|
}
|
||||||
|
t
|
||||||
|
}
|
||||||
|
|
||||||
|
def notifyAboutEndOfCode(org: Int): Unit = nextByte = org
|
||||||
|
}
|
||||||
|
|
||||||
|
class VariableAllocator(private var pointers: List[Int], private val bytes: ByteAllocator) {
|
||||||
|
|
||||||
|
private var pointerMap = mutable.Map[Int, Set[VariableVertex]]()
|
||||||
|
private var variableMap = mutable.Map[Int, mutable.Map[Int, Set[VariableVertex]]]()
|
||||||
|
|
||||||
|
var onEachByte: (Int => Unit) = _
|
||||||
|
|
||||||
|
def allocatePointer(callGraph: CallGraph, p: VariableVertex): Int = {
|
||||||
|
pointerMap.foreach { case (addr, alreadyThere) =>
|
||||||
|
if (alreadyThere.forall(q => callGraph.canOverlap(p, q))) {
|
||||||
|
pointerMap(addr) += p
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pointers match {
|
||||||
|
case Nil =>
|
||||||
|
ErrorReporting.fatal("Out of zero-page memory")
|
||||||
|
case next :: rest =>
|
||||||
|
pointers = rest
|
||||||
|
onEachByte(next)
|
||||||
|
onEachByte(next + 1)
|
||||||
|
pointerMap(next) = Set(p)
|
||||||
|
next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def allocateByte(callGraph: CallGraph, p: VariableVertex, options: CompilationOptions): Int = allocateBytes(callGraph, p, options, 1)
|
||||||
|
|
||||||
|
def allocateBytes(callGraph: CallGraph, p: VariableVertex, options: CompilationOptions, count: Int): Int = {
|
||||||
|
if (!variableMap.contains(count)) {
|
||||||
|
variableMap(count) = mutable.Map()
|
||||||
|
}
|
||||||
|
variableMap(count).foreach { case (a, alreadyThere) =>
|
||||||
|
if (alreadyThere.forall(q => callGraph.canOverlap(p, q))) {
|
||||||
|
variableMap(count)(a) += p
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val addr = bytes.allocateBytes(count, options)
|
||||||
|
(addr to (addr + count)).foreach(onEachByte)
|
||||||
|
variableMap(count)(addr) = Set(p)
|
||||||
|
addr
|
||||||
|
}
|
||||||
|
|
||||||
|
def notifyAboutEndOfCode(org: Int): Unit = bytes.notifyAboutEndOfCode(org)
|
||||||
|
}
|
435
src/main/scala/millfork/parser/MfParser.scala
Normal file
435
src/main/scala/millfork/parser/MfParser.scala
Normal file
|
@ -0,0 +1,435 @@
|
||||||
|
package millfork.parser
|
||||||
|
|
||||||
|
import java.nio.file.{Files, Paths}
|
||||||
|
|
||||||
|
import fastparse.all._
|
||||||
|
import millfork.assembly.{AddrMode, Opcode}
|
||||||
|
import millfork.env._
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node._
|
||||||
|
import millfork.{CompilationOptions, SeparatedList}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
case class MfParser(filename: String, input: String, currentDirectory: String, options: CompilationOptions) {
|
||||||
|
|
||||||
|
var lastPosition = Position(filename, 1, 1, 0)
|
||||||
|
var lastLabel = ""
|
||||||
|
|
||||||
|
def toAst: Parsed[Program] = program.parse(input + "\n\n\n")
|
||||||
|
|
||||||
|
private val lineStarts: Array[Int] = (0 +: input.zipWithIndex.filter(_._1 == '\n').map(_._2)).toArray
|
||||||
|
|
||||||
|
def position(label: String = ""): P[Position] = Index.map(i => indexToPosition(i, label))
|
||||||
|
|
||||||
|
def indexToPosition(i: Int, label: String): Position = {
|
||||||
|
val prefix = lineStarts.takeWhile(_ <= i)
|
||||||
|
val newPosition = Position(filename, prefix.length, i - prefix.last, i)
|
||||||
|
if (newPosition.cursor > lastPosition.cursor) {
|
||||||
|
lastPosition = newPosition
|
||||||
|
lastLabel = label
|
||||||
|
}
|
||||||
|
newPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
val comment: P[Unit] = P("//" ~/ CharsWhile(c => c != '\n' && c != '\r', min = 0) ~ ("\r\n" | "\r" | "\n"))
|
||||||
|
|
||||||
|
val SWS: P[Unit] = P(CharsWhileIn(" \t", min = 1)).opaque("<horizontal whitespace>")
|
||||||
|
|
||||||
|
val HWS: P[Unit] = P(CharsWhileIn(" \t", min = 0)).opaque("<horizontal whitespace>")
|
||||||
|
|
||||||
|
val AWS: P[Unit] = P((CharIn(" \t\n\r;") | NoCut(comment)).rep(min = 0)).opaque("<any whitespace>")
|
||||||
|
|
||||||
|
val EOL: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | comment).opaque("<first line break>") ~ AWS).opaque("<line break>")
|
||||||
|
|
||||||
|
val letter: P[String] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_").!)
|
||||||
|
|
||||||
|
val letterOrDigit: P[Unit] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_.$1234567890"))
|
||||||
|
|
||||||
|
val lettersOrDigits: P[String] = P(CharsWhileIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_.$1234567890", min = 0).!)
|
||||||
|
|
||||||
|
val identifier: P[String] = P((letter ~ lettersOrDigits).map { case (a, b) => a + b }).opaque("<identifier>")
|
||||||
|
|
||||||
|
// def operator: P[String] = P(CharsWhileIn("!-+*/><=~|&^", min=1).!) // TODO: only valid operators
|
||||||
|
|
||||||
|
// TODO: 3-byte types
|
||||||
|
def size(value: Int, wordLiteral: Boolean, longLiteral: Boolean): Int =
|
||||||
|
if (value > 255 || value < -128 || wordLiteral)
|
||||||
|
if (value > 0xffff || longLiteral) 4 else 2
|
||||||
|
else 1
|
||||||
|
|
||||||
|
def sign(abs: Int, minus: Boolean): Int = if (minus) -abs else abs
|
||||||
|
|
||||||
|
val decimalAtom: P[LiteralExpression] =
|
||||||
|
for {
|
||||||
|
p <- position()
|
||||||
|
minus <- "-".!.?
|
||||||
|
s <- CharsWhileIn("1234567890", min = 1).!.opaque("<decimal digits>") ~ !("x" | "b")
|
||||||
|
} yield {
|
||||||
|
val abs = Integer.parseInt(s, 10)
|
||||||
|
val value = sign(abs, minus.isDefined)
|
||||||
|
LiteralExpression(value, size(value, s.length > 3, s.length > 5)).pos(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
val binaryAtom: P[LiteralExpression] =
|
||||||
|
for {
|
||||||
|
p <- position()
|
||||||
|
minus <- "-".!.?
|
||||||
|
_ <- P("0b" | "%") ~/ Pass
|
||||||
|
s <- CharsWhileIn("01", min = 1).!.opaque("<binary digits>")
|
||||||
|
} yield {
|
||||||
|
val abs = Integer.parseInt(s, 2)
|
||||||
|
val value = sign(abs, minus.isDefined)
|
||||||
|
LiteralExpression(value, size(value, s.length > 8, s.length > 16)).pos(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
val hexAtom: P[LiteralExpression] =
|
||||||
|
for {
|
||||||
|
p <- position()
|
||||||
|
minus <- "-".!.?
|
||||||
|
_ <- P("0x" | "$") ~/ Pass
|
||||||
|
s <- CharsWhileIn("1234567890abcdefABCDEF", min = 1).!.opaque("<hex digits>")
|
||||||
|
} yield {
|
||||||
|
val abs = Integer.parseInt(s, 16)
|
||||||
|
val value = sign(abs, minus.isDefined)
|
||||||
|
LiteralExpression(value, size(value, s.length > 2, s.length > 4)).pos(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
val literalAtom: P[LiteralExpression] = binaryAtom | hexAtom | decimalAtom
|
||||||
|
|
||||||
|
val atom: P[Expression] = P(literalAtom | (position() ~ identifier).map { case (p, i) => VariableExpression(i).pos(p) })
|
||||||
|
|
||||||
|
val mlOperators = List(
|
||||||
|
List("+=", "-=", "+'=", "-'=", "^=", "&=", "|=", "*=", "*'=", "<<=", ">>=", "<<'=", ">>'="),
|
||||||
|
List("||", "^^"),
|
||||||
|
List("&&"),
|
||||||
|
List("==", "<=", ">=", "!=", "<", ">"),
|
||||||
|
List(":"),
|
||||||
|
List("+'", "-'", "<<'", ">>'", ">>>>", "+", "-", "&", "|", "^", "<<", ">>"),
|
||||||
|
List("*'", "*"))
|
||||||
|
|
||||||
|
val nonStatementLevel = 1 // everything but not `=`
|
||||||
|
val mathLevel = 4 // the `:` operator
|
||||||
|
|
||||||
|
def flags(allowed: String*): P[Set[String]] = StringIn(allowed: _*).!.rep(min = 0, sep = SWS).map(_.toSet).opaque("<flags>")
|
||||||
|
|
||||||
|
def variableDefinition(implicitlyGlobal: Boolean): P[DeclarationStatement] = for {
|
||||||
|
p <- position()
|
||||||
|
flags <- flags("const", "static", "volatile", "stack") ~ HWS
|
||||||
|
typ <- identifier ~ SWS
|
||||||
|
name <- identifier ~/ HWS ~/ Pass
|
||||||
|
addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("<address>") ~ HWS
|
||||||
|
initialValue <- ("=" ~/ HWS ~/ mlExpression(1)).? ~ HWS
|
||||||
|
_ <- &(EOL) ~/ ""
|
||||||
|
} yield {
|
||||||
|
VariableDeclarationStatement(name, typ,
|
||||||
|
global = implicitlyGlobal || flags("static"),
|
||||||
|
stack = flags("stack"),
|
||||||
|
constant = flags("const"),
|
||||||
|
volatile = flags("volatile"),
|
||||||
|
initialValue, addr).pos(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
val externFunctionBody: P[Option[List[Statement]]] = P("extern" ~/ PassWith(None))
|
||||||
|
|
||||||
|
val paramDefinition: P[ParameterDeclaration] = for {
|
||||||
|
p <- position()
|
||||||
|
typ <- identifier ~/ SWS ~/ Pass
|
||||||
|
name <- identifier ~/ Pass
|
||||||
|
} yield {
|
||||||
|
ParameterDeclaration(typ, ByVariable(name)).pos(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
val appcSimple: P[ParamPassingConvention] = P("xy" | "yx" | "ax" | "ay" | "xa" | "ya" | "stack" | "a" | "x" | "y").!.map {
|
||||||
|
case "xy" => ByRegister(Register.XY)
|
||||||
|
case "yx" => ByRegister(Register.YX)
|
||||||
|
case "ax" => ByRegister(Register.AX)
|
||||||
|
case "ay" => ByRegister(Register.AY)
|
||||||
|
case "xa" => ByRegister(Register.XA)
|
||||||
|
case "ya" => ByRegister(Register.YA)
|
||||||
|
case "a" => ByRegister(Register.A)
|
||||||
|
case "x" => ByRegister(Register.X)
|
||||||
|
case "y" => ByRegister(Register.Y)
|
||||||
|
case x => ErrorReporting.fatal(s"Unknown assembly parameter passing convention: `$x`")
|
||||||
|
}
|
||||||
|
|
||||||
|
val appcComplex: P[ParamPassingConvention] = P((("const" | "ref").! ~/ AWS).? ~ AWS ~ identifier) map {
|
||||||
|
case (None, name) => ByVariable(name)
|
||||||
|
case (Some("const"), name) => ByConstant(name)
|
||||||
|
case (Some("ref"), name) => ByReference(name)
|
||||||
|
case x => ErrorReporting.fatal(s"Unknown assembly parameter passing convention: `$x`")
|
||||||
|
}
|
||||||
|
|
||||||
|
val asmParamDefinition: P[ParameterDeclaration] = for {
|
||||||
|
p <- position()
|
||||||
|
typ <- identifier ~ SWS
|
||||||
|
appc <- appcSimple | appcComplex
|
||||||
|
} yield ParameterDeclaration(typ, appc).pos(p)
|
||||||
|
|
||||||
|
|
||||||
|
val arrayListContents: P[List[Expression]] = ("[" ~/ AWS ~/ mlExpression(nonStatementLevel).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ "]" ~/ Pass).map(_.toList)
|
||||||
|
|
||||||
|
val doubleQuotedString: P[List[Char]] = P("\"" ~/ CharsWhile(c => c != '\"' && c != '\n' && c != '\r').! ~ "\"").map(_.toList)
|
||||||
|
|
||||||
|
val codec: P[TextCodec] = P(position() ~ identifier).map {
|
||||||
|
case (_, "ascii") => TextCodec.Ascii
|
||||||
|
case (_, "petscii") => TextCodec.Petscii
|
||||||
|
case (_, "pet") => TextCodec.Petscii
|
||||||
|
case (p, x) =>
|
||||||
|
ErrorReporting.error(s"Unknown string encoding: `$x`", Some(p))
|
||||||
|
TextCodec.Ascii
|
||||||
|
}
|
||||||
|
|
||||||
|
def arrayFileContents: P[List[Expression]] = for {
|
||||||
|
p <- "file" ~ HWS ~/ "(" ~/ HWS ~/ position()
|
||||||
|
filePath <- doubleQuotedString ~/ HWS
|
||||||
|
optSlice <- ("," ~/ HWS ~/ literalAtom ~/ HWS ~/ "," ~/ HWS ~/ literalAtom ~/ HWS ~/ Pass).?
|
||||||
|
_ <- ")" ~/ Pass
|
||||||
|
} yield {
|
||||||
|
val data = Files.readAllBytes(Paths.get(currentDirectory, filePath.mkString))
|
||||||
|
val slice = optSlice.fold(data) {
|
||||||
|
case (start, length) => data.drop(start.value.toInt).take(length.value.toInt)
|
||||||
|
}
|
||||||
|
slice.map(c => LiteralExpression(c & 0xff, 1)).toList
|
||||||
|
}
|
||||||
|
|
||||||
|
def arrayStringContents: P[List[Expression]] = P(position() ~ doubleQuotedString ~/ HWS ~ codec).map {
|
||||||
|
case (p, s, co) => s.map(c => LiteralExpression(co.decode(None, c), 1).pos(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
def arrayContents: P[List[Expression]] = arrayListContents | arrayFileContents | arrayStringContents
|
||||||
|
|
||||||
|
def arrayDefinition: P[ArrayDeclarationStatement] = for {
|
||||||
|
p <- position()
|
||||||
|
name <- "array" ~ !letterOrDigit ~/ SWS ~ identifier ~ HWS
|
||||||
|
length <- ("[" ~/ AWS ~/ mlExpression(nonStatementLevel) ~ AWS ~ "]").? ~ HWS
|
||||||
|
addr <- ("@" ~/ HWS ~/ mlExpression(1)).? ~/ HWS
|
||||||
|
contents <- ("=" ~/ HWS ~/ arrayContents).? ~/ HWS
|
||||||
|
} yield ArrayDeclarationStatement(name, length, addr, contents).pos(p)
|
||||||
|
|
||||||
|
def tightMlExpression: P[Expression] = P(mlParenExpr | functionCall | mlIndexedExpression | atom) // TODO
|
||||||
|
|
||||||
|
def mlExpression(level: Int): P[Expression] = {
|
||||||
|
val allowedOperators = mlOperators.drop(level).flatten
|
||||||
|
|
||||||
|
def inner: P[SeparatedList[Expression, String]] = {
|
||||||
|
for {
|
||||||
|
head <- tightMlExpression ~/ HWS
|
||||||
|
maybeOperator <- StringIn(allowedOperators: _*).!.?
|
||||||
|
maybeTail <- maybeOperator.fold[P[Option[List[(String, Expression)]]]](Pass.map(_ => None))(o => (HWS ~/ inner ~/ HWS).map(x2 => Some((o -> x2.head) :: x2.tail)))
|
||||||
|
} yield {
|
||||||
|
maybeTail.fold[SeparatedList[Expression, String]](SeparatedList.of(head))(t => SeparatedList(head, t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def p(list: SeparatedList[Expression, String], level: Int): Expression =
|
||||||
|
if (level == mlOperators.length) list.head
|
||||||
|
else {
|
||||||
|
val xs = list.split(mlOperators(level).toSet(_))
|
||||||
|
xs.separators.distinct match {
|
||||||
|
case Nil =>
|
||||||
|
if (xs.tail.nonEmpty)
|
||||||
|
ErrorReporting.error("Too many different operators")
|
||||||
|
p(xs.head, level + 1)
|
||||||
|
case List("+") | List("-") | List("+", "-") | List("-", "+") =>
|
||||||
|
SumExpression(xs.toPairList("+").map { case (op, value) => (op == "-", p(value, level + 1)) }, decimal = false)
|
||||||
|
case List("+'") | List("-'") | List("+'", "-'") | List("-'", "+'") =>
|
||||||
|
SumExpression(xs.toPairList("+").map { case (op, value) => (op == "-", p(value, level + 1)) }, decimal = true)
|
||||||
|
case List(":") =>
|
||||||
|
if (xs.size != 2) {
|
||||||
|
ErrorReporting.error("The `:` operator can have only two arguments", xs.head.head.position)
|
||||||
|
LiteralExpression(0, 1)
|
||||||
|
} else {
|
||||||
|
SeparateBytesExpression(p(xs.head, level + 1), p(xs.tail.head._2, level + 1))
|
||||||
|
}
|
||||||
|
case List(op) =>
|
||||||
|
FunctionCallExpression(op, xs.items.map(value => p(value, level + 1)))
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Too many different operators")
|
||||||
|
LiteralExpression(0, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.map(x => p(x, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
def mlLhsExpressionSimple: P[LhsExpression] = mlIndexedExpression | (position() ~ identifier).map { case (p, n) => VariableExpression(n).pos(p) }
|
||||||
|
|
||||||
|
def mlLhsExpression: P[LhsExpression] = {
|
||||||
|
val separated = position() ~ mlLhsExpressionSimple ~ HWS ~ ":" ~/ HWS ~ mlLhsExpressionSimple
|
||||||
|
separated.map { case (p, h, l) => SeparateBytesExpression(h, l).pos(p) } | mlLhsExpressionSimple
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def mlParenExpr: P[Expression] = P("(" ~/ AWS ~/ mlExpression(nonStatementLevel) ~ AWS ~/ ")")
|
||||||
|
|
||||||
|
def mlIndexedExpression: P[IndexedExpression] = for {
|
||||||
|
p <- position()
|
||||||
|
array <- identifier
|
||||||
|
index <- HWS ~ "[" ~/ AWS ~/ mlExpression(nonStatementLevel) ~ AWS ~/ "]"
|
||||||
|
} yield IndexedExpression(array, index).pos(p)
|
||||||
|
|
||||||
|
def functionCall: P[FunctionCallExpression] = for {
|
||||||
|
p <- position()
|
||||||
|
name <- identifier
|
||||||
|
params <- HWS ~ "(" ~/ AWS ~/ mlExpression(nonStatementLevel).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ ""
|
||||||
|
} yield FunctionCallExpression(name, params.toList).pos(p)
|
||||||
|
|
||||||
|
val expressionStatement: P[ExecutableStatement] = mlExpression(0).map(ExpressionStatement)
|
||||||
|
|
||||||
|
val assignmentStatement: P[ExecutableStatement] =
|
||||||
|
(position() ~ mlLhsExpression ~ HWS ~ "=" ~/ HWS ~ mlExpression(1)).map {
|
||||||
|
case (p, l, r) => Assignment(l, r).pos(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
def keywordStatement: P[ExecutableStatement] = P(returnStatement | ifStatement | whileStatement | forStatement | doWhileStatement | inlineAssembly | assignmentStatement)
|
||||||
|
|
||||||
|
def executableStatement: P[ExecutableStatement] = (position() ~ P(keywordStatement | expressionStatement)).map { case (p, s) => s.pos(p) }
|
||||||
|
|
||||||
|
// TODO: label and instruction in one line
|
||||||
|
def asmLabel: P[ExecutableStatement] = (identifier ~ HWS ~ ":" ~/ HWS).map(l => AssemblyStatement(Opcode.LABEL, AddrMode.DoesNotExist, VariableExpression(l), elidable = true))
|
||||||
|
|
||||||
|
// def zeropageAddrModeHint: P[Option[Boolean]] = Pass
|
||||||
|
|
||||||
|
def asmOpcode: P[Opcode.Value] = (position() ~ letter.rep(exactly = 3).!).map { case (p, o) => Opcode.lookup(o, Some(p)) }
|
||||||
|
|
||||||
|
def asmExpression: P[Expression] = (position() ~ NoCut(
|
||||||
|
("<" ~/ HWS ~ mlExpression(mathLevel)).map(e => HalfWordExpression(e, hiByte = false)) |
|
||||||
|
(">" ~/ HWS ~ mlExpression(mathLevel)).map(e => HalfWordExpression(e, hiByte = true)) |
|
||||||
|
mlExpression(mathLevel)
|
||||||
|
)).map { case (p, e) => e.pos(p) }
|
||||||
|
|
||||||
|
val commaX = HWS ~ "," ~ HWS ~ ("X" | "x") ~ HWS
|
||||||
|
val commaY = HWS ~ "," ~ HWS ~ ("Y" | "y") ~ HWS
|
||||||
|
|
||||||
|
def asmParameter: P[(AddrMode.Value, Expression)] = {
|
||||||
|
(SWS ~ (
|
||||||
|
("#" ~ asmExpression).map(AddrMode.Immediate -> _) |
|
||||||
|
("(" ~ HWS ~ asmExpression ~ HWS ~ ")" ~ commaY).map(AddrMode.IndexedY -> _) |
|
||||||
|
("(" ~ HWS ~ asmExpression ~ commaX ~ ")").map(AddrMode.IndexedX -> _) |
|
||||||
|
("(" ~ HWS ~ asmExpression ~ HWS ~ ")").map(AddrMode.Indirect -> _) |
|
||||||
|
(asmExpression ~ commaX).map(AddrMode.AbsoluteX -> _) |
|
||||||
|
(asmExpression ~ commaY).map(AddrMode.AbsoluteY -> _) |
|
||||||
|
asmExpression.map(AddrMode.Absolute -> _)
|
||||||
|
)).?.map(_.getOrElse(AddrMode.Implied -> LiteralExpression(0, 1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
def elidable: P[Boolean] = ("?".! ~/ HWS).?.map(_.isDefined)
|
||||||
|
|
||||||
|
def asmInstruction: P[ExecutableStatement] = {
|
||||||
|
val lineParser: P[(Boolean, Opcode.Value, (AddrMode.Value, Expression))] = !"}" ~ elidable ~/ asmOpcode ~/ asmParameter
|
||||||
|
lineParser.map { case (elid, op, param) =>
|
||||||
|
AssemblyStatement(op, param._1, param._2, elid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def asmStatement: P[ExecutableStatement] = (position("assembly statement") ~ P(asmLabel | asmInstruction)).map { case (p, s) => s.pos(p) } // TODO: macros
|
||||||
|
|
||||||
|
def statement: P[Statement] = (position() ~ P(keywordStatement | variableDefinition(false) | expressionStatement)).map { case (p, s) => s.pos(p) }
|
||||||
|
|
||||||
|
def asmStatements: P[List[ExecutableStatement]] = ("{" ~/ AWS ~/ asmStatement.rep(sep = EOL ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList)
|
||||||
|
|
||||||
|
def statements: P[List[Statement]] = ("{" ~/ AWS ~ statement.rep(sep = EOL ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList)
|
||||||
|
|
||||||
|
def executableStatements: P[Seq[ExecutableStatement]] = "{" ~/ AWS ~/ executableStatement.rep(sep = EOL ~ !"}" ~/ Pass) ~/ AWS ~ "}"
|
||||||
|
|
||||||
|
def returnStatement: P[ExecutableStatement] = ("return" ~ !letterOrDigit ~/ HWS ~ mlExpression(nonStatementLevel).?).map(ReturnStatement)
|
||||||
|
|
||||||
|
def ifStatement: P[ExecutableStatement] = for {
|
||||||
|
condition <- "if" ~ !letterOrDigit ~/ HWS ~/ mlExpression(nonStatementLevel)
|
||||||
|
thenBranch <- AWS ~/ executableStatements
|
||||||
|
elseBranch <- (AWS ~ "else" ~/ AWS ~/ executableStatements).?
|
||||||
|
} yield IfStatement(condition, thenBranch.toList, elseBranch.getOrElse(Nil).toList)
|
||||||
|
|
||||||
|
def whileStatement: P[ExecutableStatement] = for {
|
||||||
|
condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mlExpression(nonStatementLevel)
|
||||||
|
body <- AWS ~ executableStatements
|
||||||
|
} yield WhileStatement(condition, body.toList)
|
||||||
|
|
||||||
|
def forDirection: P[ForDirection.Value] =
|
||||||
|
("parallel" ~ HWS ~ "to").!.map(_ => ForDirection.ParallelTo) |
|
||||||
|
("parallel" ~ HWS ~ "until").!.map(_ => ForDirection.ParallelUntil) |
|
||||||
|
"until".!.map(_ => ForDirection.Until) |
|
||||||
|
"to".!.map(_ => ForDirection.To) |
|
||||||
|
("down" ~/ HWS ~/ "to").!.map(_ => ForDirection.DownTo)
|
||||||
|
|
||||||
|
def forStatement: P[ExecutableStatement] = for {
|
||||||
|
identifier <- "for" ~ SWS ~/ identifier ~/ "," ~/ Pass
|
||||||
|
start <- mlExpression(nonStatementLevel) ~ HWS ~ "," ~/ HWS ~/ Pass
|
||||||
|
direction <- forDirection ~/ HWS ~/ "," ~/ HWS ~/ Pass
|
||||||
|
end <- mlExpression(nonStatementLevel)
|
||||||
|
body <- AWS ~ executableStatements
|
||||||
|
} yield ForStatement(identifier, start, end, direction, body.toList)
|
||||||
|
|
||||||
|
def inlineAssembly: P[ExecutableStatement] = for {
|
||||||
|
condition <- "asm" ~ !letterOrDigit ~/ Pass
|
||||||
|
body <- AWS ~ asmStatements
|
||||||
|
} yield BlockStatement(body)
|
||||||
|
|
||||||
|
//noinspection MutatorLikeMethodIsParameterless
|
||||||
|
def doWhileStatement: P[ExecutableStatement] = for {
|
||||||
|
body <- "do" ~ !letterOrDigit ~/ AWS ~ executableStatements ~/ AWS
|
||||||
|
condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mlExpression(nonStatementLevel)
|
||||||
|
} yield DoWhileStatement(body.toList, condition)
|
||||||
|
|
||||||
|
def functionDefinition: P[DeclarationStatement] = for {
|
||||||
|
p <- position()
|
||||||
|
flags <- flags("asm", "inline", "interrupt", "reentrant") ~ HWS
|
||||||
|
returnType <- identifier ~ SWS
|
||||||
|
name <- identifier ~ HWS
|
||||||
|
params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS
|
||||||
|
addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("<address>") ~/ AWS
|
||||||
|
statements <- (externFunctionBody | (if (flags("asm")) asmStatements else statements).map(l => Some(l))) ~/ Pass
|
||||||
|
} yield {
|
||||||
|
if (flags("interrupt") && flags("inline")) ErrorReporting.error(s"Interrupt function `$name` cannot be inline", Some(p))
|
||||||
|
if (flags("interrupt") && flags("reentrant")) ErrorReporting.error("Interrupt function `$name` cannot be reentrant", Some(p))
|
||||||
|
if (flags("inline") && flags("reentrant")) ErrorReporting.error("Reentrant and inline exclude each other", Some(p))
|
||||||
|
if (flags("interrupt") && returnType != "void") ErrorReporting.error("Interrupt function `$name` has to return void", Some(p))
|
||||||
|
if (addr.isEmpty && statements.isEmpty) ErrorReporting.error("Extern function `$name` must have an address", Some(p))
|
||||||
|
if (statements.isEmpty && !flags("asm") && params.nonEmpty) ErrorReporting.error("Extern non-asm function `$name` cannot have parameters", Some(p))
|
||||||
|
if (flags("asm")) statements match {
|
||||||
|
case Some(Nil) => ErrorReporting.warn("Assembly function `$name` is empty, did you mean RTS or RTI", options, Some(p))
|
||||||
|
case Some(xs) =>
|
||||||
|
if (flags("interrupt")) {
|
||||||
|
if (xs.exists {
|
||||||
|
case AssemblyStatement(Opcode.RTS, _, _, _) => true
|
||||||
|
case _ => false
|
||||||
|
}) ErrorReporting.warn("Assembly interrupt function `$name` contains RTS, did you mean RTI?", options, Some(p))
|
||||||
|
} else {
|
||||||
|
if (xs.exists {
|
||||||
|
case AssemblyStatement(Opcode.RTI, _, _, _) => true
|
||||||
|
case _ => false
|
||||||
|
}) ErrorReporting.warn("Assembly non-interrupt function `$name` contains RTI, did you mean RTS?", options, Some(p))
|
||||||
|
}
|
||||||
|
if (!flags("inline")) {
|
||||||
|
xs.last match {
|
||||||
|
case AssemblyStatement(Opcode.RTS, _, _, _) => () // OK
|
||||||
|
case AssemblyStatement(Opcode.RTI, _, _, _) => () // OK
|
||||||
|
case AssemblyStatement(Opcode.JMP, _, _, _) => () // OK
|
||||||
|
case _ =>
|
||||||
|
val validReturn = if (flags("interrupt")) "RTI" else "RTS"
|
||||||
|
ErrorReporting.warn(s"Non-inline assembly function `$name` should end in " + validReturn, options, Some(p))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case None => ()
|
||||||
|
}
|
||||||
|
FunctionDeclarationStatement(name, returnType, params.toList,
|
||||||
|
addr,
|
||||||
|
statements,
|
||||||
|
flags("inline"),
|
||||||
|
flags("asm"),
|
||||||
|
flags("interrupt"),
|
||||||
|
flags("reentrant")).pos(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
def importStatement: Parser[ImportStatement] = ("import" ~ !letterOrDigit ~/ SWS ~/ identifier).map(ImportStatement)
|
||||||
|
|
||||||
|
def program: Parser[Program] = for {
|
||||||
|
_ <- Start ~/ AWS ~/ Pass
|
||||||
|
definitions <- (importStatement | arrayDefinition | functionDefinition | variableDefinition(true)).rep(sep = EOL)
|
||||||
|
_ <- AWS ~ End
|
||||||
|
} yield Program(definitions.toList)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
24
src/main/scala/millfork/parser/MinimalTestCase.scala
Normal file
24
src/main/scala/millfork/parser/MinimalTestCase.scala
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package millfork.parser
|
||||||
|
|
||||||
|
import fastparse.all._
|
||||||
|
import fastparse.core
|
||||||
|
|
||||||
|
object MinimalTestCase {
|
||||||
|
def AWS: P[Unit] = "\n".rep(min = 0).opaque("<any whitespace>").log()
|
||||||
|
|
||||||
|
def EOL: P[Unit] = "\n".rep(min = 1).opaque("<line break>").log()
|
||||||
|
|
||||||
|
def identifier: P[String] = CharPred(_.isLetter).rep(min = 1).!.opaque("<identifier>").log()
|
||||||
|
|
||||||
|
def identifierWithSpace: P[String] = (identifier ~/ AWS ~/ Pass).opaque("<identifier with space>").log()
|
||||||
|
|
||||||
|
def separator: P[Unit] = ("," ~/ AWS ~/ Pass).opaque("<comma>").log()
|
||||||
|
|
||||||
|
def identifiers: P[Seq[String]] = identifierWithSpace.rep(min = 0, sep = separator)//.opaque("<separated identifiers>").log()
|
||||||
|
|
||||||
|
def array: P[Seq[String]] = ("[" ~/ AWS ~/ identifiers ~/ "]" ~/ Pass)//.opaque("<array>").log()
|
||||||
|
|
||||||
|
def arrays: Parser[Seq[Seq[String]]] = (array ~/ EOL).rep(min = 0, sep = !End ~/ Pass)//.opaque("<arrays>").log()
|
||||||
|
|
||||||
|
def program: Parser[Seq[Seq[String]]] = Start ~/ AWS ~/ arrays ~/ End
|
||||||
|
}
|
169
src/main/scala/millfork/parser/ParserBase.scala
Normal file
169
src/main/scala/millfork/parser/ParserBase.scala
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
package millfork.parser
|
||||||
|
|
||||||
|
import millfork.node.Position
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
case class ParseException(msg: String, position: Option[Position]) extends Exception
|
||||||
|
|
||||||
|
class ParserBase(filename: String, input: String) {
|
||||||
|
|
||||||
|
def reset(): Unit = {
|
||||||
|
cursor = 0
|
||||||
|
line = 1
|
||||||
|
column = FirstColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
private val FirstColumn = 0
|
||||||
|
private val length = input.length
|
||||||
|
private var cursor = 0
|
||||||
|
private var line = 1
|
||||||
|
private var column = FirstColumn
|
||||||
|
|
||||||
|
def position = Position(filename, line, column, cursor)
|
||||||
|
|
||||||
|
def restorePosition(p: Position): Unit = {
|
||||||
|
cursor = p.cursor
|
||||||
|
column = p.column
|
||||||
|
line = p.line
|
||||||
|
}
|
||||||
|
|
||||||
|
def error(msg: String, pos: Option[Position]): Nothing = throw ParseException(msg, pos)
|
||||||
|
|
||||||
|
def error(msg: String, pos: Position): Nothing = throw ParseException(msg, Some(pos))
|
||||||
|
|
||||||
|
def error(msg: String): Nothing = throw ParseException(msg, Some(position))
|
||||||
|
|
||||||
|
def error() = throw ParseException("Syntax error", Some(position))
|
||||||
|
|
||||||
|
def nextChar() = {
|
||||||
|
if (cursor >= length) error("Unexpected end of input")
|
||||||
|
val c = input(cursor)
|
||||||
|
cursor += 1
|
||||||
|
if (c == '\n') {
|
||||||
|
line += 1
|
||||||
|
column = FirstColumn
|
||||||
|
} else {
|
||||||
|
column += 1
|
||||||
|
}
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
def peekChar(): Char = {
|
||||||
|
if (cursor >= length) '\0' else input(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
def require(char: Char): Char = {
|
||||||
|
val pos = position
|
||||||
|
val c = nextChar()
|
||||||
|
if (c != char) error(s"Expected `$char`", pos)
|
||||||
|
c
|
||||||
|
}
|
||||||
|
def require(p: Char=>Boolean, errorMsg: String = "Unexpected character"): Char = {
|
||||||
|
val pos = position
|
||||||
|
val c = nextChar()
|
||||||
|
if (!p(c)) error(errorMsg, pos)
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
def require(s: String): String = {
|
||||||
|
val c = peekChars(s.length)
|
||||||
|
if (c != s) error(s"Expected `$s`")
|
||||||
|
1 to s.length foreach (_=>nextChar())
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
def requireAny(s: String, errorMsg: String = "Unexpected character"): Char = {
|
||||||
|
val c = nextChar()
|
||||||
|
if (s.contains(c)) c
|
||||||
|
else error(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
def peek2Chars(): String = {
|
||||||
|
peekChars(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
def peekChars(n: Int): String = {
|
||||||
|
if (cursor > length - n) input.substring(cursor) else input.substring(cursor, cursor + n)
|
||||||
|
}
|
||||||
|
|
||||||
|
def charsWhile(pred: Char => Boolean, min: Int = 0, errorMsg: String = "Unexpected character"): String = {
|
||||||
|
val sb = new StringBuilder()
|
||||||
|
while (pred(peekChar())) {
|
||||||
|
sb += nextChar()
|
||||||
|
}
|
||||||
|
val s = sb.toString
|
||||||
|
if (s.length < min) error(errorMsg)
|
||||||
|
else s
|
||||||
|
}
|
||||||
|
|
||||||
|
def skipNextIfMatches(c: Char): Boolean = {
|
||||||
|
if (peekChar() == c) {
|
||||||
|
nextChar()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def either(c: Char, s: String): Unit = {
|
||||||
|
if (peekChar() == c) {
|
||||||
|
nextChar()
|
||||||
|
} else if (peekChars(s.length) == s) {
|
||||||
|
require(s)
|
||||||
|
} else {
|
||||||
|
error(s"Expected either `$c` or `$s`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def sepOrEnd(sep: Char, end: Char): Boolean = {
|
||||||
|
val p = position
|
||||||
|
val c = nextChar()
|
||||||
|
if (c == sep) true
|
||||||
|
else if (c==end) false
|
||||||
|
else error(s"Expected `$sep` or `$end`", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
def anyOf[T](errorMsg: String, alternatives: (()=> T)*): T = {
|
||||||
|
alternatives.foreach { t =>
|
||||||
|
val p = position
|
||||||
|
try {
|
||||||
|
return t()
|
||||||
|
} catch {
|
||||||
|
case _: ParseException => restorePosition(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
def surrounded[T](left: => Any, content: => T, right: => Any): T = {
|
||||||
|
left
|
||||||
|
val result = content
|
||||||
|
right
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
def followed[T](content: => T, right: => Any): T = {
|
||||||
|
val result = content
|
||||||
|
right
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
def attempt[T](content: => T): Option[T] = {
|
||||||
|
val p = position
|
||||||
|
try {
|
||||||
|
Some(content)
|
||||||
|
} catch {
|
||||||
|
case _: ParseException => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def opaque[T](errorMsg: String)(block: =>T) :T={
|
||||||
|
try {
|
||||||
|
block
|
||||||
|
} catch{
|
||||||
|
case p:ParseException => error(errorMsg, p.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
src/main/scala/millfork/parser/SourceLoadingQueue.scala
Normal file
89
src/main/scala/millfork/parser/SourceLoadingQueue.scala
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package millfork.parser
|
||||||
|
|
||||||
|
import java.nio.file.{Files, Paths}
|
||||||
|
|
||||||
|
import fastparse.core.Parsed.{Failure, Success}
|
||||||
|
import millfork.CompilationOptions
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node.{ImportStatement, Position, Program}
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
class SourceLoadingQueue(val initialFilenames: List[String], val includePath: List[String], val options: CompilationOptions) {
|
||||||
|
|
||||||
|
private val parsedModules = mutable.Map[String, Program]()
|
||||||
|
private val moduleQueue = mutable.Queue[() => Unit]()
|
||||||
|
val extension: String = ".ml"
|
||||||
|
|
||||||
|
|
||||||
|
def run(): Program = {
|
||||||
|
initialFilenames.foreach { i =>
|
||||||
|
parseModule(extractName(i), includePath, Right(i), options)
|
||||||
|
}
|
||||||
|
options.platform.startingModules.foreach {m =>
|
||||||
|
moduleQueue.enqueue(() => parseModule(m, includePath, Left(None), options))
|
||||||
|
}
|
||||||
|
while (moduleQueue.nonEmpty) {
|
||||||
|
moduleQueue.dequeueAll(_ => true).par.foreach(_())
|
||||||
|
}
|
||||||
|
ErrorReporting.assertNoErrors("Parse failed")
|
||||||
|
parsedModules.values.reduce(_ + _)
|
||||||
|
}
|
||||||
|
|
||||||
|
def lookupModuleFile(includePath: List[String], moduleName: String, position: Option[Position]): String = {
|
||||||
|
includePath.foreach { dir =>
|
||||||
|
val file = Paths.get(dir, moduleName + extension).toFile
|
||||||
|
ErrorReporting.debug("Checking " + file)
|
||||||
|
if (file.exists()) {
|
||||||
|
return file.getAbsolutePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErrorReporting.fatal(s"Module `$moduleName` not found", position)
|
||||||
|
}
|
||||||
|
|
||||||
|
def parseModule(moduleName: String, includePath: List[String], why: Either[Option[Position], String], options: CompilationOptions): Unit = {
|
||||||
|
val filename: String = why.fold(p => lookupModuleFile(includePath, moduleName, p), s => s)
|
||||||
|
ErrorReporting.debug(s"Parsing $filename")
|
||||||
|
val path = Paths.get(filename)
|
||||||
|
val parentDir = path.toFile.getAbsoluteFile.getParent
|
||||||
|
val src = new String(Files.readAllBytes(path))
|
||||||
|
val parser = MfParser(filename, src, parentDir, options)
|
||||||
|
parser.toAst match {
|
||||||
|
case Success(prog, _) =>
|
||||||
|
parsedModules.synchronized {
|
||||||
|
parsedModules.put(moduleName, prog)
|
||||||
|
prog.declarations.foreach {
|
||||||
|
case s@ImportStatement(m) =>
|
||||||
|
if (!parsedModules.contains(m)) {
|
||||||
|
moduleQueue.enqueue(() => parseModule(m, parentDir :: includePath, Left(s.position), options))
|
||||||
|
}
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case f@Failure(a, b, d) =>
|
||||||
|
ErrorReporting.error(s"Failed to parse the module `$moduleName` in $filename", Some(parser.indexToPosition(f.index, parser.lastLabel)))
|
||||||
|
// ErrorReporting.error(a.toString)
|
||||||
|
// ErrorReporting.error(b.toString)
|
||||||
|
// ErrorReporting.error(d.toString)
|
||||||
|
// ErrorReporting.error(d.traced.expected)
|
||||||
|
// ErrorReporting.error(d.traced.stack.toString)
|
||||||
|
// ErrorReporting.error(d.traced.traceParsers.toString)
|
||||||
|
// ErrorReporting.error(d.traced.fullStack.toString)
|
||||||
|
// ErrorReporting.error(f.toString)
|
||||||
|
if (parser.lastLabel != "") {
|
||||||
|
ErrorReporting.error(s"Syntax error: ${parser.lastLabel} expected", Some(parser.lastPosition))
|
||||||
|
} else {
|
||||||
|
ErrorReporting.error("Syntax error", Some(parser.lastPosition))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def extractName(i: String): String = {
|
||||||
|
val noExt = i.stripSuffix(extension)
|
||||||
|
val lastSlash = noExt.lastIndexOf('/') max noExt.lastIndexOf('\\')
|
||||||
|
if (lastSlash >= 0) i.substring(lastSlash + 1) else i
|
||||||
|
}
|
||||||
|
}
|
32
src/main/scala/millfork/parser/TextCodec.scala
Normal file
32
src/main/scala/millfork/parser/TextCodec.scala
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package millfork.parser
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node.Position
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
class TextCodec(val name:String, private val map: String, private val extra: Map[Char,Int]) {
|
||||||
|
def decode(position: Option[Position], c: Char): Int = {
|
||||||
|
if (extra.contains(c)) extra(c) else {
|
||||||
|
val index = map.indexOf(c)
|
||||||
|
if (index >= 0) {
|
||||||
|
index
|
||||||
|
} else {
|
||||||
|
ErrorReporting.fatal("Invalid character in string in ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object TextCodec {
|
||||||
|
val NotAChar = '\ufffd'
|
||||||
|
|
||||||
|
val Ascii = new TextCodec("ASCII", 0.until(127).map{i => if (i<32) NotAChar else i.toChar}.mkString, Map.empty)
|
||||||
|
|
||||||
|
val Petscii = new TextCodec("PETSCII",
|
||||||
|
"\ufffd" * 32 + 0x20.to(0x3f).map(_.toChar).mkString + "@abcdefghijklmnopqrstuvwxyz[£]↑←–ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||||
|
Map('^' -> 0x5E, 'π' -> 0x7E)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
75
src/test/java/com/grapeshot/halfnes/CPURAM.java
Normal file
75
src/test/java/com/grapeshot/halfnes/CPURAM.java
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package com.grapeshot.halfnes;
|
||||||
|
|
||||||
|
import com.grapeshot.halfnes.mappers.Mapper;
|
||||||
|
import millfork.output.MemoryBank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since the original CPURAM class was a convoluted mess of dependencies,
|
||||||
|
* I overrode it with mine that has only few pieces of junk glue to make it work
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class CPURAM {
|
||||||
|
|
||||||
|
private final MemoryBank mem;
|
||||||
|
|
||||||
|
// required by the CPU class for some reason
|
||||||
|
public Mapper mapper = new Mapper() {
|
||||||
|
@Override
|
||||||
|
public TVType getTVType() {
|
||||||
|
// the base class returns null, but this can't be null
|
||||||
|
return TVType.DENDY;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// required by the CPU class for some reason
|
||||||
|
public APU apu = new APU(null, null, this);
|
||||||
|
|
||||||
|
public CPURAM(MemoryBank mem) {
|
||||||
|
boolean[] readable = mem.readable();
|
||||||
|
boolean[] writeable = mem.writeable();
|
||||||
|
for (int i = 0xfffe; i >= 0; i--) {
|
||||||
|
if (readable[i]) {
|
||||||
|
// allow for dummy fetches by implied instructions
|
||||||
|
readable[i + 1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readable[0] = true;
|
||||||
|
readable[1] = true;
|
||||||
|
readable[2] = true;
|
||||||
|
for (int i = 0x100; i <= 0x1ff; i++) {
|
||||||
|
readable[i] = true;
|
||||||
|
writeable[i] = true;
|
||||||
|
}
|
||||||
|
for (int i = 0x4000; i <= 0x407f; i++) {
|
||||||
|
readable[i] = true;
|
||||||
|
writeable[i] = true;
|
||||||
|
}
|
||||||
|
for (int i = 0xc000; i <= 0xcfff; i++) {
|
||||||
|
readable[i] = true;
|
||||||
|
writeable[i] = true;
|
||||||
|
}
|
||||||
|
for (int i = 0xfffa; i <= 0xffff; i++) {
|
||||||
|
readable[i] = true;
|
||||||
|
writeable[i] = true;
|
||||||
|
}
|
||||||
|
this.mem = mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public final int read(int addr) {
|
||||||
|
addr &= 0xffff;
|
||||||
|
if (!mem.readable()[addr]) {
|
||||||
|
throw new RuntimeException("Can't read from $" + Integer.toHexString(addr));
|
||||||
|
}
|
||||||
|
return mem.output()[addr] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void write(int addr, int data) {
|
||||||
|
addr &= 0xffff;
|
||||||
|
if (!mem.writeable()[addr]) {
|
||||||
|
throw new RuntimeException("Can't write to $" + Integer.toHexString(addr));
|
||||||
|
}
|
||||||
|
mem.output()[addr] = (byte) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
133
src/test/scala/millfork/test/ArraySuite.scala
Normal file
133
src/test/scala/millfork/test/ArraySuite.scala
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package millfork.test
|
||||||
|
|
||||||
|
import millfork.{Cpu, OptimizationPresets}
|
||||||
|
import millfork.assembly.opt.{AlwaysGoodOptimizations, DangerousOptimizations}
|
||||||
|
import millfork.test.emu._
|
||||||
|
import org.scalatest.{FunSuite, Matchers}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
class ArraySuite extends FunSuite with Matchers {
|
||||||
|
|
||||||
|
test("Array assignment") {
|
||||||
|
val m = EmuSuperOptimizedRun(
|
||||||
|
"""
|
||||||
|
| array output [3] @$c000
|
||||||
|
| array input = [5,6,7]
|
||||||
|
| void main () {
|
||||||
|
| copyEntry(0)
|
||||||
|
| copyEntry(1)
|
||||||
|
| copyEntry(2)
|
||||||
|
| }
|
||||||
|
| void copyEntry(byte index) {
|
||||||
|
| output[index] = input[index]
|
||||||
|
| }
|
||||||
|
""".stripMargin)
|
||||||
|
m.readByte(0xc000) should equal(5)
|
||||||
|
m.readByte(0xc001) should equal(6)
|
||||||
|
m.readByte(0xc002) should equal(7)
|
||||||
|
|
||||||
|
}
|
||||||
|
test("Array assignment with offset") {
|
||||||
|
EmuUltraBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| array output [8] @$c000
|
||||||
|
| void main () {
|
||||||
|
| byte i
|
||||||
|
| i = 0
|
||||||
|
| while i != 6 {
|
||||||
|
| output[i + 2] = i + 1
|
||||||
|
| output[i] = output[i]
|
||||||
|
| i += 1
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin) { m =>
|
||||||
|
m.readByte(0xc002) should equal(1)
|
||||||
|
m.readByte(0xc007) should equal(6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Array assignment with offset 1") {
|
||||||
|
val m = new EmuRun(Cpu.StrictMos, Nil, DangerousOptimizations.All ++ OptimizationPresets.Good, true)(
|
||||||
|
"""
|
||||||
|
| array output [8] @$c000
|
||||||
|
| void main () {
|
||||||
|
| byte i
|
||||||
|
| i = 0
|
||||||
|
| while i != 6 {
|
||||||
|
| output[i + 2] = i + 1
|
||||||
|
| output[i] = output[i]
|
||||||
|
| i += 1
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin)
|
||||||
|
m.readByte(0xc002) should equal(1)
|
||||||
|
m.readByte(0xc007) should equal(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Array assignment through a pointer") {
|
||||||
|
val m = EmuUnoptimizedRun(
|
||||||
|
"""
|
||||||
|
| array output [3] @$c000
|
||||||
|
| pointer p
|
||||||
|
| void main () {
|
||||||
|
| p = output.addr
|
||||||
|
| byte i
|
||||||
|
| byte ignored
|
||||||
|
| i = 1
|
||||||
|
| word w
|
||||||
|
| w = $105
|
||||||
|
| p[i]:ignored = w
|
||||||
|
| }
|
||||||
|
""".stripMargin)
|
||||||
|
m.readByte(0xc001) should equal(1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Array in place math") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| array output [4] @$c000
|
||||||
|
| void main () {
|
||||||
|
| byte i
|
||||||
|
| i = 3
|
||||||
|
| output[i] = 3
|
||||||
|
| output[i + 1 - 1] *= 4
|
||||||
|
| output[3] *= 5
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc003) should equal(60))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Array simple read") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$c000
|
||||||
|
| array a[7]
|
||||||
|
| void main () {
|
||||||
|
| byte i
|
||||||
|
| i = 6
|
||||||
|
| a[i] = 6
|
||||||
|
| output = a[i]
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(6))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Array simple read 2") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| word output @$c000
|
||||||
|
| array a[7]
|
||||||
|
| void main () {
|
||||||
|
| output = 777
|
||||||
|
| byte i
|
||||||
|
| i = 6
|
||||||
|
| a[i] = 6
|
||||||
|
| output = a[i]
|
||||||
|
| }
|
||||||
|
""".stripMargin){m =>
|
||||||
|
m.readByte(0xc000) should equal(6)
|
||||||
|
m.readByte(0xc001) should equal(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
281
src/test/scala/millfork/test/AssemblyOptimizationSuite.scala
Normal file
281
src/test/scala/millfork/test/AssemblyOptimizationSuite.scala
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
package millfork.test
|
||||||
|
|
||||||
|
import millfork.{Cpu, OptimizationPresets}
|
||||||
|
import millfork.assembly.opt.{AlwaysGoodOptimizations, LaterOptimizations, VariableToRegisterOptimization}
|
||||||
|
import millfork.test.emu.{EmuBenchmarkRun, EmuUltraBenchmarkRun, EmuRun}
|
||||||
|
import org.scalatest.{FunSuite, Matchers}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
class AssemblyOptimizationSuite extends FunSuite with Matchers {
|
||||||
|
|
||||||
|
test("Duplicate RTS") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| void main () {
|
||||||
|
| if 1 == 1 {
|
||||||
|
| return
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin) { _ => }
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Inlining variable") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| array output [5] @$C000
|
||||||
|
| void main () {
|
||||||
|
| byte i
|
||||||
|
| i = 0
|
||||||
|
| while (i<5) {
|
||||||
|
| output[i] = i
|
||||||
|
| i += 1
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc003) should equal(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Loading modified variables") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$C000
|
||||||
|
| void main () {
|
||||||
|
| byte x
|
||||||
|
| output = 5
|
||||||
|
| output += 1
|
||||||
|
| output += 1
|
||||||
|
| output += 1
|
||||||
|
| x = output
|
||||||
|
| output = x
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(8))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Bit ops") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$C000
|
||||||
|
| void main () {
|
||||||
|
| output ^= output
|
||||||
|
| output |= 5 | 6
|
||||||
|
| output |= 5 | 6
|
||||||
|
| output &= 5 & 6
|
||||||
|
| output ^= 8 ^ 16
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(28))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Inlining after a while") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| array output [2]@$C000
|
||||||
|
| void main () {
|
||||||
|
| byte i
|
||||||
|
| output[0] = 6
|
||||||
|
| lol()
|
||||||
|
| i = 1
|
||||||
|
| if (i > 0) {
|
||||||
|
| output[i] = 4
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
| void lol() {}
|
||||||
|
""".stripMargin)(_.readWord(0xc000) should equal(0x406))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Tail call") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$C000
|
||||||
|
| void main () {
|
||||||
|
| if (output != 55) {
|
||||||
|
| output += 1
|
||||||
|
| main()
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(55))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("LDA-TAY elimination") {
|
||||||
|
new EmuRun(Cpu.StrictMos, OptimizationPresets.NodeOpt, List(VariableToRegisterOptimization, AlwaysGoodOptimizations.YYY), false)(
|
||||||
|
"""
|
||||||
|
| array mouse_pointer[64]
|
||||||
|
| array arrow[64]
|
||||||
|
| byte output @$C000
|
||||||
|
| void main () {
|
||||||
|
| byte i
|
||||||
|
| i = 0
|
||||||
|
| while i < 63 {
|
||||||
|
| mouse_pointer[i] = arrow[i]
|
||||||
|
| i += 1
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Carry flag after AND-LSR") {
|
||||||
|
EmuUltraBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$C000
|
||||||
|
| void main () {
|
||||||
|
| output = f(5)
|
||||||
|
| }
|
||||||
|
| byte f(byte x) {
|
||||||
|
| return ((x & $1E) >> 1) + 3
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(5))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Index sequence") {
|
||||||
|
EmuUltraBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| array output[6] @$C000
|
||||||
|
| void main () {
|
||||||
|
| pointer o
|
||||||
|
| o = output.addr
|
||||||
|
| o[3] = 8
|
||||||
|
| o[4] = 8
|
||||||
|
| o[5] = 8
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
""".stripMargin){m =>
|
||||||
|
m.readByte(0xc005) should equal(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Index switching") {
|
||||||
|
EmuUltraBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| array output1[6] @$C000
|
||||||
|
| array output2[6] @$C010
|
||||||
|
| array input[6] @$C010
|
||||||
|
| void main () {
|
||||||
|
| static byte a
|
||||||
|
| static byte b
|
||||||
|
| input[5] = 3
|
||||||
|
| a = five()
|
||||||
|
| b = five()
|
||||||
|
| output1[a] = input[b]
|
||||||
|
| output2[a] = input[b]
|
||||||
|
| }
|
||||||
|
| byte five() {
|
||||||
|
| return 5
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
""".stripMargin){m =>
|
||||||
|
m.readByte(0xc005) should equal(3)
|
||||||
|
m.readByte(0xc015) should equal(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("TAX-BCC-RTS-TXA optimization") {
|
||||||
|
new EmuRun(Cpu.StrictMos,
|
||||||
|
OptimizationPresets.NodeOpt, List(
|
||||||
|
AlwaysGoodOptimizations.PointlessStoreAfterLoad,
|
||||||
|
LaterOptimizations.PointlessLoadAfterStore,
|
||||||
|
VariableToRegisterOptimization,
|
||||||
|
LaterOptimizations.DoubleLoadToDifferentRegisters,
|
||||||
|
LaterOptimizations.DoubleLoadToTheSameRegister,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfers,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.PointlessLoadBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfers,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.PointlessLoadBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad,
|
||||||
|
AlwaysGoodOptimizations.PointlessStashingToIndexOverShortSafeBranch,
|
||||||
|
AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeReturn,
|
||||||
|
AlwaysGoodOptimizations.IdempotentDuplicateRemoval,
|
||||||
|
AlwaysGoodOptimizations.IdempotentDuplicateRemoval,
|
||||||
|
AlwaysGoodOptimizations.IdempotentDuplicateRemoval), false)(
|
||||||
|
"""
|
||||||
|
| byte output @$C000
|
||||||
|
| void main(){ delta() }
|
||||||
|
| byte delta () {
|
||||||
|
| output = 0
|
||||||
|
| byte mouse_delta
|
||||||
|
| mouse_delta = 6
|
||||||
|
| mouse_delta &= $3f
|
||||||
|
| if mouse_delta >= $20 {
|
||||||
|
| mouse_delta |= $c0
|
||||||
|
| return 3
|
||||||
|
| }
|
||||||
|
| return mouse_delta
|
||||||
|
| }
|
||||||
|
""".stripMargin).readByte(0xc000) should equal(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Memory access detection"){
|
||||||
|
EmuUltraBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| array h [4] @$C000
|
||||||
|
| array l [4] @$C404
|
||||||
|
| word output @$C00C
|
||||||
|
| word a @$C200
|
||||||
|
| void main () {
|
||||||
|
| byte i
|
||||||
|
| a = 0x102
|
||||||
|
| barrier()
|
||||||
|
| for i,0,until,4 {
|
||||||
|
| h[i]:l[i] = a
|
||||||
|
| }
|
||||||
|
| a.lo:a.hi=a
|
||||||
|
| output = a
|
||||||
|
| }
|
||||||
|
| void barrier (){}
|
||||||
|
|
|
||||||
|
""".stripMargin){m =>
|
||||||
|
m.readByte(0xc000) should equal(1)
|
||||||
|
m.readByte(0xc001) should equal(1)
|
||||||
|
m.readByte(0xc002) should equal(1)
|
||||||
|
m.readByte(0xc003) should equal(1)
|
||||||
|
m.readByte(0xc404) should equal(2)
|
||||||
|
m.readByte(0xc405) should equal(2)
|
||||||
|
m.readByte(0xc406) should equal(2)
|
||||||
|
m.readByte(0xc407) should equal(2)
|
||||||
|
m.readWord(0xc00c) should equal(0x201)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Memory access detection 2"){
|
||||||
|
EmuUltraBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| array h [4]
|
||||||
|
| array l [4]
|
||||||
|
| word output @$C00C
|
||||||
|
| word ptrh @$C000
|
||||||
|
| word ptrl @$C002
|
||||||
|
| void main () {
|
||||||
|
| ptrh = h.addr
|
||||||
|
| ptrl = l.addr
|
||||||
|
| byte i
|
||||||
|
| word a
|
||||||
|
| a = 0x102
|
||||||
|
| barrier()
|
||||||
|
| for i,0,until,4 {
|
||||||
|
| h[i]:l[i] = a
|
||||||
|
| }
|
||||||
|
| a.lo:a.hi=a
|
||||||
|
| output = a
|
||||||
|
| couput
|
||||||
|
| }
|
||||||
|
| void barrier (){}
|
||||||
|
|
|
||||||
|
""".stripMargin){m =>
|
||||||
|
val ptrh = 0xffff & m.readWord(0xC000)
|
||||||
|
val ptrl = 0xffff & m.readWord(0xC002)
|
||||||
|
m.readByte(ptrh + 0) should equal(1)
|
||||||
|
m.readByte(ptrh + 1) should equal(1)
|
||||||
|
m.readByte(ptrh + 2) should equal(1)
|
||||||
|
m.readByte(ptrh + 3) should equal(1)
|
||||||
|
m.readByte(ptrl + 0) should equal(2)
|
||||||
|
m.readByte(ptrl + 1) should equal(2)
|
||||||
|
m.readByte(ptrl + 2) should equal(2)
|
||||||
|
m.readByte(ptrl + 3) should equal(2)
|
||||||
|
m.readWord(0xc00c) should equal(0x201)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
src/test/scala/millfork/test/AssemblySuite.scala
Normal file
99
src/test/scala/millfork/test/AssemblySuite.scala
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package millfork.test
|
||||||
|
import millfork.test.emu.EmuBenchmarkRun
|
||||||
|
import org.scalatest.{FunSuite, Matchers}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
class AssemblySuite extends FunSuite with Matchers {
|
||||||
|
|
||||||
|
test("Inline assembly") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$c000
|
||||||
|
| void main () {
|
||||||
|
| output = 0
|
||||||
|
| asm {
|
||||||
|
| inc $c000
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Assembly functions") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$c000
|
||||||
|
| void main () {
|
||||||
|
| output = 0
|
||||||
|
| thing()
|
||||||
|
| }
|
||||||
|
| asm void thing() {
|
||||||
|
| inc $c000
|
||||||
|
| rts
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Empty assembly") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$c000
|
||||||
|
| void main () {
|
||||||
|
| output = 1
|
||||||
|
| asm {}
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Passing params to assembly") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$c000
|
||||||
|
| void main () {
|
||||||
|
| output = f(5)
|
||||||
|
| }
|
||||||
|
| asm byte f(byte a) {
|
||||||
|
| clc
|
||||||
|
| adc #5
|
||||||
|
| rts
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(10))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Inline asm functions") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$c000
|
||||||
|
| void main () {
|
||||||
|
| output = 0
|
||||||
|
| f()
|
||||||
|
| f()
|
||||||
|
| }
|
||||||
|
| inline asm void f() {
|
||||||
|
| inc $c000
|
||||||
|
| rts
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Inline asm functions 2") {
|
||||||
|
EmuBenchmarkRun(
|
||||||
|
"""
|
||||||
|
| byte output @$c000
|
||||||
|
| void main () {
|
||||||
|
| output = 0
|
||||||
|
| add(output, 5)
|
||||||
|
| add(output, 5)
|
||||||
|
| }
|
||||||
|
| inline asm void add(byte ref v, byte const c) {
|
||||||
|
| lda v
|
||||||
|
| clc
|
||||||
|
| adc #c
|
||||||
|
| sta v
|
||||||
|
| rts
|
||||||
|
| }
|
||||||
|
""".stripMargin)(_.readByte(0xc000) should equal(5))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user