pulley_interpreter

Macro for_each_op

Source
macro_rules! for_each_op {
    ( $macro:ident ) => { ... };
}
Expand description

Calls the given macro with each opcode.

§Instruction Guidelines

We’re inventing an instruction set here which naturally brings a whole set of design questions. Note that this is explicitly intended to be only ever used for Pulley where there are a different set of design constraints than other instruction sets (e.g. general-purpose CPU ISAs). Some examples of constraints for Pulley are:

  • Instructions must be portable to many architectures.
  • The Pulley ISA is mostly target-independent as the compilation target is currently only parameterized on pointer width and endianness.
  • Pulley instructions should be balance of time-to-decode and code size. For example super fancy bit-packing tricks might be tough to decode in software but might be worthwhile if it’s quite common and greatly reduces the size of bytecode. There’s not a hard-and-fast answer here, but a balance to be made.
  • Many “macro ops” are present to reduce the size of compiled bytecode so there is a wide set of duplicate functionality between opcodes (and this is expected).

Given all this it’s also useful to have a set of guidelines used to name and develop Pulley instructions. As of the time of this writing it’s still pretty early days for Pulley so some of these guidelines may change over time. Additionally instructions don’t necessarily all follow these conventions and that may also change over time. With that in mind, here’s a rough set of guidelines:

  • Most instructions are prefixed with x, f, or v, indicating which type of register they’re operating on. (e.g. xadd32 operates on the x integer registers and fadd32 operates on the f float registers).

  • Most instructions are suffixed or otherwise contain the bit width they’re operating on. For example xadd32 is a 32-bit addition.

  • If an instruction operates on signed or unsigned data (such as division and remainder), then the instruction is suffixed with _s or _u.

  • Instructions operate on either 32 or 64-bit parts of a register. Instructions modifying only 32-bits of a register always modify the “low” part of a register and leave the upper part unmodified. This is intended to help 32-bit platforms where if most operations are 32-bit there’s no need for extra instructions to sign or zero extend and modify the upper half of the register.

  • Binops use BinaryOperands<T> for the destination and argument registers.

  • Instructions operating on memory contain a few pieces of information:

    xload16le_u32_offset32
    │└─┬┘└┤└┤ └┬┘ └──┬───┘
    │  │  │ │  │     ▼
    │  │  │ │  │     addressing mode
    │  │  │ │  ▼
    │  │  │ │  width of register modified + sign-extension (optional)
    │  │  │ ▼
    │  │  │ endianness of the operation (le/be)
    │  │  ▼
    │  │  bit-width of the operation
    │  ▼
    │  what's happening (load/store)
    ▼
    register being operated on (x/f/z)

More guidelines might get added here over time, and if you have any questions feel free to raise them and we can try to add them here as well!