Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preprocessor for ULP/RTC macros #43

Merged
merged 29 commits into from
Aug 9, 2021

Conversation

wnienhaus
Copy link
Collaborator

This PR implements issue #19 and in a way also issue #12, because now many examples from the net can be assembled unmodified.

This is a big PR. I therefore split it up into many small commits, each standing on its own and intended to be short and simple to review. It will be best to review this PR one commit at a time. Each commit describes in more detail what the change is.

Each commit has at least unit tests (that also pass) covering the change. All changes also have compat tests (even though they sometimes have their own commit), to ensure the resulting binary always matches what binutils-esp32ulp would have produced. There are also a new set of compat tests, that download sources from the net (including the examples from the tests of binutils-esp32ulp) and run them through both py-esp32-ulp and binutils-esp32ulp to ensure the output is the same).

In summary, the PR has 4 changes:

  • Fixes encountered during testing or to support the change.
  • Adding support for expression evaluation to the assembler (e.g. 1+1 or 1<<symbol)
  • Adding a preprocessor for preprocessing ESP-IDF macros/constants before the code goes to the assembler
  • Add a new suite of compat tests using example code from the net as input

I originally set myself the goal to be able to compile all examples from this repo (https://github.com/duff2013/ulptool/tree/master/src/ulp_examples) without modification to the sources. I will say I managed (including it working on the ESP32 itself), except for the cases that use assembler macros (see issue #40), which I intentionally did not implement. (I might try that as a future side project).

The back story to this all is that I used to do a lot of manual steps with my ULP assembly source code. I passed it through the gcc preprocessor to get the macros I used expanded, but those macros spit out long expressions with countless brackets. Manually evaluating those (especially when using my own constants in the expressions) was tedious. I also kept the original code (before preprocessing) in comments above the resulting statements, so I could more easily understand where the final reg_rd or reg_wr statements came from. While that helped, keeping the statements and comments in sync was also messy.

Ultimately, I wanted a better way. I never used binutils-esp32ulp to compile my ULP sources, because when I started with ULP coding, I didn't want to download and build this massive tool on my machine. I found py-esp32-ulp and it was just so simple and fast and did what I needed. So while using biunutils-esp32ulp is an alternative (especially, when creating a docker image of it), I wanted to continue with the lightweight and simple approach of py-esp32-ulp, directly in MicroPython. (Of course this also seemed like a fun side project, which is definitely was! In particular, getting it to work on the actual device with little memory!)

@wnienhaus
Copy link
Collaborator Author

Actually... it fixes issue #16 too (commit with subject: "add support for the .global directive. only symbols flagged as global will be exported")

@ThomasWaldmann
Copy link
Collaborator

Wow, this is big. :-)

Guess I'll need a bit for reviewing this. Could you please work on #44 while I do that?

Would be good to have master branch testing successfully "as is" and then rebase this PR onto new master.

@wnienhaus
Copy link
Collaborator Author

Will do. Started looking into making the Travis build pass - the failure is because of the old gcc on Ubuntu Trusty. But if you want to move to Github Actions, I will look into that!

.travis.yml Outdated Show resolved Hide resolved
@ThomasWaldmann
Copy link
Collaborator

just merged another PR of yours into master, so refresh master and rebase again.

@wnienhaus
Copy link
Collaborator Author

So. It's rebased now. And all tests pass :) .

@micropython micropython deleted a comment from wnienhaus Aug 3, 2021
tests/assemble.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@ThomasWaldmann ThomasWaldmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

started reviewing:

add units test for the .set directive 5a9b7e9
add support for left aligned assembler directives (e.g. .set) … 8d7981d
fix a crash bug where BSS size calculation was attemped on the value … … 45b3769
raise error when attempting to store values in .bss section … f65cbc2
fix reference to non-exiting variable f0d86db
fixed typo in comment of instruction definition 15633eb
add support for the .global directive. only symbols flagged as global… … 3a2d312
let SymbolTable.export() optionally export non-global symbols too … 2417778

i only found a few nitpicks. overall, great stuff so far. i like how you do clean commits!

tests/assemble.py Show resolved Hide resolved
esp32_ulp/assemble.py Show resolved Hide resolved
esp32_ulp/assemble.py Outdated Show resolved Hide resolved
tests/assemble.py Outdated Show resolved Hide resolved
tests/assemble.py Outdated Show resolved Hide resolved
@wnienhaus
Copy link
Collaborator Author

For now, I have pushed the fixed as additional commits (because I dont know what force pushing will do to this PR's history). But I want to rebase/squash them later, so they "original" commits are clean, without those commits.

Much open-source code out there has .global, .set, etc directives starting
in the first column of a line. This change allows assembling such code.

Incidentally this also fixes a bug, where directives without parameters,
such as .text, .data, etc were silently accepted when left-aligned but
in those cases treated as labels instead of section headers.
@wnienhaus wnienhaus force-pushed the preprocessor branch 2 times, most recently from f78130e to 5f76237 Compare August 5, 2021 05:28
Copy link
Collaborator

@ThomasWaldmann ThomasWaldmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed:
support ULP opcodes in upper case … 33b4d3d
add a compatibilty test for the recent fixes and improvements … 9c94247
add support for evaluating expressions … 4962139
add a compatibilty test for evaluating expressions … be27cdc
docs: add that expressions are suppported 521e924

guess that eval() is dangerous.

tests/assemble.py Outdated Show resolved Hide resolved
tests/assemble.py Show resolved Hide resolved
esp32_ulp/opcodes.py Outdated Show resolved Hide resolved
esp32_ulp/opcodes.py Outdated Show resolved Hide resolved
esp32_ulp/opcodes.py Outdated Show resolved Hide resolved
Comment on lines 309 to 313
try:
entry = symbols.get_sym(arg)
return ARG(SYM, entry, arg)
except KeyError:
pass
return ARG(IMM, int(eval_arg(arg)), arg)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    try:
        entry = symbols.get_sym(arg)
    except KeyError:
        return ARG(IMM, int(eval_arg(arg)), arg)
    else:
        return ARG(SYM, entry, arg)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seen my previous comment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed now.

esp32_ulp/util.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@ThomasWaldmann ThomasWaldmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed:
add preprocessor that can replace simple #define values in code … d5c274f
fix evaluation of expressions during first assembler pass … 5480e1d
remove no-longer-needed pass dependent code from SymbolTable … 5811060

esp32_ulp/__main__.py Outdated Show resolved Hide resolved
esp32_ulp/preprocess.py Show resolved Hide resolved
esp32_ulp/preprocess.py Outdated Show resolved Hide resolved
esp32_ulp/preprocess.py Show resolved Hide resolved
esp32_ulp/preprocess.py Outdated Show resolved Hide resolved
esp32_ulp/assemble.py Outdated Show resolved Hide resolved
@wnienhaus wnienhaus force-pushed the preprocessor branch 3 times, most recently from 806bcf2 to 1606b5d Compare August 7, 2021 10:54
@wnienhaus
Copy link
Collaborator Author

For now I am done fixing what I could. Happy to get more feedback.

esp32_ulp/preprocess.py Outdated Show resolved Hide resolved
esp32_ulp/preprocess.py Outdated Show resolved Hide resolved
esp32_ulp/preprocess.py Show resolved Hide resolved
esp32_ulp/preprocess.py Outdated Show resolved Hide resolved
esp32_ulp/preprocess.py Outdated Show resolved Hide resolved
esp32_ulp/opcodes.py Outdated Show resolved Hide resolved
esp32_ulp/preprocess.py Outdated Show resolved Hide resolved
esp32_ulp/util.py Outdated Show resolved Hide resolved
tests/preprocess.py Show resolved Hide resolved
@ThomasWaldmann
Copy link
Collaborator

BTW, i first thought "OMG, this DefinesDB is a bit too much". But considering the memory issues lurking and that the db has to be built only once, guess this is the only non-cumbersome method we can use.

The only (cumbersome) alternative would be to extract "used symbols only" from the large header files.

@wnienhaus wnienhaus force-pushed the preprocessor branch 2 times, most recently from 0e932f8 to 4f70602 Compare August 8, 2021 15:25
This defines what the preprocessor aims to do, why and what its
intentional limitations are.

Examples on how to use it and how to use the "Defines DB" are also
provided
This change allows specifying the address in 32-bit words (i.e. the
address as seen from the ULP), in addition to the existing mode of
specifying a register's full address on the DPORT bus.

If an address is between 0 and DR_REG_MAX_DIRECT (0x3ff), treat it
as a word offset (ULP address), otherwise treat it as a full address
on the DPORT bus as before.
.long and .int are the same as per GNU assembler manual:
https://sourceware.org/binutils/docs/as/Long.html

binutils-esp32ulp also treats them the same (compat test included
to verify this)
@wnienhaus
Copy link
Collaborator Author

Ok, I have cleaned up everything, and rebased/squashed it all into as clean commits as I could.

I updated the preprocessor docs too, since there's now the convenience function for preprocessing which defaults to using the DefinesDB.

In one of the unit test files, I built an assert_raises method, which makes the test read nicer. I did not copy the approach to other unit test files - this could come in a refactoring commit at some later stage.

I also built a context manager for opening and closing the DefinesDB when preprocessing or parsing include files into the defines db. That definitely makes the code look cleaner. Thank you for that suggestion.

You can now take a 2nd pass over the commits - they should be pretty close to what they were like before, but with the improvements embedded into them.

@ThomasWaldmann
Copy link
Collaborator

BTW, please add yourself either to the LICENSE file or add a new file AUTHORS and reference that from LICENSE (see borgbackup/borg for an example).

Copy link
Collaborator

@ThomasWaldmann ThomasWaldmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, did a 2nd pass and found some minor stuff.

please only fix these without extending scope, do additional commits for now.

esp32_ulp/assemble.py Outdated Show resolved Hide resolved
esp32_ulp/assemble.py Outdated Show resolved Hide resolved
Comment on lines 309 to 313
try:
entry = symbols.get_sym(arg)
return ARG(SYM, entry, arg)
except KeyError:
pass
return ARG(IMM, int(eval_arg(arg)), arg)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seen my previous comment?

esp32_ulp/util.py Outdated Show resolved Hide resolved
Comment on lines 29 to 41
#test that expressions evaluate correctly for all supported operators
move r3, 1+2
move r3, 3-5
move r3, -5
move r3, 2*3
move r3, 4/2
move r3, 4 % 3
move r3, 0xff << 2
move r3, 0xff >> 1
move r3, (0xabcdef | 0xff) & 0xff
move r3, 0x1234 & ~2
move r3, 42|4&0xf # 46 (4&0xf is evaluated first)
move r3, (42|4)&0xf # 14 (42|4 is evaluated first)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bit strange mix of a real-world code example and some lines of assembler testing.

maybe separate these lines at least?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean into a separate file, or maybe I can move them below the exit: halt line and add a comment that they are all "artificial" examples?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whatever you like better.

esp32_ulp/preprocess.py Outdated Show resolved Hide resolved
esp32_ulp/preprocess.py Show resolved Hide resolved
tests/fixtures/incl.h Outdated Show resolved Hide resolved
tests/fixtures/incl2.h Outdated Show resolved Hide resolved
Comment on lines +113 to +128
class ctx:
def __init__(self, db):
self._db = db

def __enter__(self):
# not opening DefinesDB - it opens itself when needed
return self._db

def __exit__(self, type, value, traceback):
if isinstance(self._db, DefinesDB):
self._db.close()

if self._defines_db:
return ctx(self._defines_db)

return ctx(self._defines)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you try having the contextmanager inside DefinesDB class?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it works, also call the context manager from the tests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to put the context manager into the DefinesDB class, but because it handles both the "with DefinesDB" and the "without a DefinesDB" case, this doesn't fit into the DefinesDB class.

I could find an elegant way to add a condition to the "with" statement - ie. only use the context manager from DefinesDB when we're actually using the DefinesDB, otherwise work with the class-internal dict.

I'll mull over this a bit more and see if I can find a way.

Regarding testing: in effect this was tested - because the existing tests exercise the code which uses the context manager, but I added another test now to check specifically that the db is actually closed too.

Copy link
Collaborator Author

@wnienhaus wnienhaus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made all the fixes now (except the Context Manger move to the DefinesDB class, which I need to think over more). I'll push this as a commit on top now, and then check the Context Manager topic.

esp32_ulp/assemble.py Show resolved Hide resolved
esp32_ulp/preprocess.py Outdated Show resolved Hide resolved
esp32_ulp/preprocess.py Show resolved Hide resolved
tests/fixtures/incl.h Outdated Show resolved Hide resolved
tests/fixtures/incl2.h Outdated Show resolved Hide resolved
Comment on lines +113 to +128
class ctx:
def __init__(self, db):
self._db = db

def __enter__(self):
# not opening DefinesDB - it opens itself when needed
return self._db

def __exit__(self, type, value, traceback):
if isinstance(self._db, DefinesDB):
self._db.close()

if self._defines_db:
return ctx(self._defines_db)

return ctx(self._defines)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to put the context manager into the DefinesDB class, but because it handles both the "with DefinesDB" and the "without a DefinesDB" case, this doesn't fit into the DefinesDB class.

I could find an elegant way to add a condition to the "with" statement - ie. only use the context manager from DefinesDB when we're actually using the DefinesDB, otherwise work with the class-internal dict.

I'll mull over this a bit more and see if I can find a way.

Regarding testing: in effect this was tested - because the existing tests exercise the code which uses the context manager, but I added another test now to check specifically that the db is actually closed too.

@wnienhaus wnienhaus mentioned this pull request Aug 9, 2021
@wnienhaus
Copy link
Collaborator Author

Pushed latest improvements as a commit on top: 5fd45a2

Copy link
Collaborator

@ThomasWaldmann ThomasWaldmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - merge and do the remaining stuff in later PRs?

@ThomasWaldmann ThomasWaldmann added this to the 1.0 milestone Aug 9, 2021
@wnienhaus
Copy link
Collaborator Author

Ok. I force pushed one small thing - moving the "made up" statement in tests/compat/expr.S to the bottom of the file with an improved comment.

And I updated the LICENCE file and added an AUTHORS file. Perhaps check if you are happy with that, and then we're good for a merge.

(For now I don't manage to find a solution to the Context Manager challenge, but that can be improved later)

AUTHORS Outdated Show resolved Hide resolved
LICENSE Outdated
Comment on lines 3 to 4
Copyright (c) 2018 Thomas Waldmann
Copyright (c) 2018 Thomas Waldmann (see AUTHORS file)
Copyright (c) 2021 Wilko Nienhaus (see AUTHORS file)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is simpler to update authors only at one place, so I suggest this:

Copyright 2018-2021 by the py-esp32-ulp authors, see AUTHORS file.

Also, I would not go into details about who has written what, that's where git log is better anyway and a file describing this will likely be always outdated.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 47d5e8a

@ThomasWaldmann ThomasWaldmann merged commit 2844227 into micropython:master Aug 9, 2021
@ThomasWaldmann
Copy link
Collaborator

Yay! \o/

@wnienhaus
Copy link
Collaborator Author

Yay! And thank you for all your effort with this big PR!

@wnienhaus wnienhaus deleted the preprocessor branch August 14, 2021 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants