Comment by raphlinus

21 hours ago

My symbolasm project[1] is exactly that: an assembler syntax designed to make programming more mentally tractable. I expect it to help for both human and LLM coders. Apologies for the spare repo, I haven't written good readmes yet.

In this syntax (though I'm not yet targeting x86_64) it would look something like a = round(fma(a, b, c)) where the variables have the appropriate vector type (and have been placed into registers with @zmm1 syntax when first assigned).

The design of the language is entirely by hand, as was the implementation when I started, but lately I have been using AI to speed implementation.

[1] https://github.com/raphlinus/symbolasm

> it would look something like a = round(fma(a, b, c))

One problem with that is that it makes it look as if the language is more expressive than it is. You might not be able to write, for example

  d = round(fma(a, b, c))

because the instruction doing that is a three-register one.

Another example, on x64 (IIRC)

  a = a + b + 42
  d = d + 4 × e
  d = 5 × d       // equivalent to d = d + 4 × d

would be valid (being LEA variants), but

  a = a + b + c
  d = d + 5 × e
  d = 7 × d

would not. So, this would be more readable, but I’m not sure it would be easier to write.

You can make it easier to write by allowing ‘obvious’ rewrites such as

  a = b + c + d

into

  a = b + c
  a = a + d

but then, you’re on your way towards a generic programming language, and where do you stop?

It also soon doesn’t get obvious, as it might be possible to avoid the data dependency in the rewrite or to do another ‘obvious’ thing, and move the instruction following that a = a + d up between the two instructions.

  • I don't allow such rewrites, you have to be explicit. If you write something that doesn't have a valid assembler encoding, its rejected at compile time.

    It helps that I'm currently targeting ARM (Cortex M33, RP2350 specifically). Old-school x86 would be much less pleasant, as it's got so many arbitrary-seeming rules of what works and what doesn't. One quirk, when optimizing for file size, are the rules for which instructions have 16 bit encodings and which need 32. But again, the plan is to have a tight feedback loop.

Have to be rather careful with such - e.g. an x86 vaddpd or an ARM fadd aren't commutative because of NaN bit patterns (even worse for fma, esp. if you have it automatically choose the operation from which arg it's destructive over).

Perhaps not significant for writing code (though maybe someone does want the NaN bit pattern to be taken from a specific arg!), but is one aspect that can easily go missing in a higher-level assembler (or esp. disassembler).

When I made an AVR assembler, I had a play around with a few ideas.

My changes were less to do with instruction representation, and more related to macros and reusable code.

https://github.com/Lerc/AvrAsm

One example of some of the bits I added, is the macros at

https://gist.github.com/Lerc/ad96329670965dc023c5ae740b433ad...

This provides a blend of macros and snips to allow

    init:
      set_stack_pointer RAMEND
      load_let_values
      call Someroutine

    ...
    ...
    ...

    Someroutine:
      letb bits_x,62
      letb bits_y,56

      lds r16,bits_x
      lds r17,bits_y
    Etc.

So the letb macro puts the constant parameter into ROM and assigns a RAM symbol for the variable. The load_let_values macro copies the ROM values into the RAM locations (essentially dropping some of requirements for sections)

The load_let_values macro manages to copy values that are defined later in the program.

I'm not sure if this is a wise use of this feature, but it was a good tricky task to attempt to implement with it.

Happy to see more people thinking about this, there's really no good reason for ASM syntax to be so dreadfully primitive and inaccessible to this day. Even something as trivially simple as namespacing instructions to indicate CPU extensions would be a welcome improvement.