kervinck / gigatron-rom

System, apps and tooling for the Gigatron TTL microcomputer
BSD 2-Clause "Simplified" License
235 stars 80 forks source link

asm.py: Make assembler state non-global and allow private labels #128

Open psr opened 4 years ago

psr commented 4 years ago

I'd like to have the capability to do a dry-run assembly of a block of code into a buffer, so that I can automatically count how many instructions it amounts to. I'd also like to be able to have structures which forbid any assembler instructions to be emitted within them - e.g.

def pop_returnstack_to_parameterstack():
    label("forth.core.R>")
    adda(...)  # asm.py functions are fine here    
    with return_stack as rs:
        # asm.py functions are forbidden here...
        rs.pop_to(tmp0)  # lo
        rs.pop_to(tmp1)  # hi
    with parameter_stack as ps:
        # ...because these operations won't be emitted in Python source order
        ps.push_from(tmp1)  # hi
        ps.push_from(tmp0)  # lo
    label(".next")
    NEXT(...)

Another thing, which is related, but really a separate issue is that I would like to be able to re-use label names without getting the "Warning: redefining..." output, but only within the scope of a single routine - i.e. there would be internal labels for local branches, and global labels for jumps and documentation etc. Both sorts should be emitted in the disassembly, but only global ones should get the warning. I was thinking that maybe private labels could be '.' prefixed, as this seems to be the style followed in dev.py - and at the top level there would be no difference between a global variable and a local variable. In the example above 'forth.core.R>' would be a global label, and '.next' would be a local label.

To allow this I'd like to wrap the various module globals in an _AssemblerState class - perhaps with some capability to chain them together so that private labels have a notion of scope.

I know that you're already doing some surgery on asm.py in #127, and there's also the work on Python3 in #121. It would be good to be able to see what's the current state of the code is, so that I can avoid trampling anything too much. I'd also like to hear anyone else's idea. I'm going to go ahead with this in my fork of asm.py, but I'd like to keep this synchronized with the master version if at all possible.

kervinck commented 4 years ago

Currently the dot-labels are a hint to myself that these should be local. In reality I get away with keeping them in a simple global table. Space is cheap. The warning is cheaper than a scope mechanism.

An old assembler just deleted all dot-labels when a non-dotted labels was defined. But that doesn't work in the current setup. What can be done is internally prefix dot-labels with a unique number that increments each time a non-dotted label is defined.

In the GCL compiler I make jump target labels unique by appending scope numbers to them.

I've always tried to minimise work on the infrastructure for as long as I can get away with it. infrastructure can become a time trap. (Shaving the yak, as you call it in your blog post). But if a simple hack can solve 90% an itch, I'm alway open to it.

kervinck commented 4 years ago

For your consideration regarding forbidding instructions. This might be achieved with align(1, 0).

kervinck commented 4 years ago

Unwinding can be achieved with a little function that reverts _romSize.

I will consider small changes that scratch an itch. I prefer to avoid big things.

psr commented 4 years ago

OK, understood.

psr commented 4 years ago

Hi, I'm not asking to re-open this issue, because I don't really have a proposal for what should be done, but I've finally hit a situation where I've had to undo asm.py calls after they have been made. In practice it was a little more complicated that reverting _romSize, as I don't really have the capability to unwind the code that called the functions.

I thought it might be of interest. I've tried to make it well commented.

https://github.com/psr/gigatron-rom/commit/26dd47c604911b786adea4c5896ed0bdffd04246

kervinck commented 4 years ago

Good to see you still have your teeth in it.

Perhaps we can encapsulate the required state in asm.py in a singleton object. And from asm.py provide a way to fork and replace them? That wouldn't hurt a lot (although I'm just dreaming here)

# Module variables because I don't feel like making a class

The main thing about this I don't want to turn the regular asm.py functions into methods, and bother the application code with a pointless self-pointer on every line.

psr commented 4 years ago

That's pretty much exactly what I'd been thinking about doing. However there's a few complexities that cropped up when I tried to do it previously - exactly the things that have made this effort complicated - cases where the operand is that is relative either to the PC or to a label.

On the other hand - being able to measure code and decide where it should be placed feels a lot cleaner than "oh no, we've gone too far!"

For now I have the code I need. At some point in the future I might come back to it.

kervinck commented 4 years ago

We have a similar issue when including application-specific SYS functions from the command line. I frequently have to shuffle the include order in the Makefile. At some point this itch becomes just too much...

psr commented 4 years ago

Shuffling by hand is exactly what I have been doing with hand-written low-level words. There's a bin-packing problem to be solved!

When my Forth is ready to bring back to the main repository it will no longer make sense to have a separate copy of asm.py, or brittle code which monkeypatches it. At that point I'll probably need to solve this properly.

kervinck commented 4 years ago

It's better to keep the issue "open" so the discussion can stay visible while you're working on it.