Writing Instructions (Developers) - ezasm-org/ezasm GitHub Wiki

Creating and Updating Instructions

EzASM uses an annotation-based instruction discovery system that automatically registers methods as executable instructions. This makes adding new instructions straightforward and maintainable. Here's how to create or update instructions:

Architecture Overview

Instructions in EzASM follow a transformation-based model where each instruction returns a TransformationSequence that describes state changes to memory, registers, or other simulator components. This design enables features like undo/redo and maintains a clean separation between instruction logic and simulator state management. The InstructionDispatcher automatically discovers and registers any method annotated with @Instruction by scanning registered instruction handler classes at runtime.

Step-by-Step Guide

1. Create or Open an Instruction Handler Class

Instruction handler classes are located in src/main/java/com/ezasm/instructions/implementation/. Group related instructions together (e.g., ArithmeticInstructions, FileIOInstructions, MemoryInstructions). Each handler class must have a constructor that accepts a Simulator instance:

public class MyInstructions {
    private final Simulator simulator;
    
    public MyInstructions(Simulator simulator) {
        this.simulator = simulator;
    }
}

2. Implement Instruction Methods

Create a public method annotated with @Instruction that returns a TransformationSequence. The method name becomes the instruction name in EzASM (case-insensitive). Method parameters must use abstraction types like IAbstractInput or IAbstractInputOutput to support various operand types (registers, memory addresses, immediates):

@Instruction
public TransformationSequence myinstruction(IAbstractInput input) throws SimulationException {
    // Get the value from the input
    int value = (int) input.get(simulator).intValue();
    
    // Perform operation
    int result = value * 2;
    
    // Create transformation to update a register
    InputOutputTransformable io = new InputOutputTransformable(
        simulator, 
        new RegisterInputOutput(Registers.R0)
    );
    Transformation t = io.transformation(new RawData(result));
    
    return new TransformationSequence(t);
}

3. Register the Handler Class

Add your new instruction handler class to the static registration block in InstructionDispatcher.java:

static {
    registerInstructions(ArithmeticInstructions.class);
    registerInstructions(MyInstructions.class);  // Add your class here
    // ... other registrations
}

4. Handle Different Operand Types

Use IAbstractInput for read-only operands and IAbstractInputOutput for operands that can be both read and written. Common patterns include reading from memory using simulator.getMemory().read(address), accessing registers via simulator.getRegisters().getRegister(name), and creating transformations for state changes using InputOutputTransformable, MemoryTransformable, or RegisterInputOutput.

5. Working with Transformations

For immediate side effects (like file I/O), return an empty TransformationSequence. For state changes that should support undo/redo, create appropriate transformations. Multiple transformations can be combined: new TransformationSequence(t1, t2, t3). Common transformable types include InputOutputTransformable for register/memory updates, MemoryTransformable for direct memory manipulation, and HeapPointerTransformable for heap management.

6. Add Documentation and Examples

Write clear JavaDoc explaining what the instruction does, its parameters, return values, and potential exceptions. Create example EzASM programs in the examples/ directory demonstrating your instruction's usage. Update or create markdown documentation in examples/ to explain the instruction's purpose and usage patterns.

Best Practices

  • Use descriptive method names that clearly indicate the instruction's purpose
  • Validate inputs and throw SimulationException with helpful error messages when operations fail
  • Follow existing patterns by examining similar instructions in other handler classes
  • Test thoroughly with edge cases, invalid inputs, and typical use scenarios
  • Handle memory carefully when working with null-terminated strings, use Memory.getWordSize() to calculate proper offsets
  • Prefix Java keywords with underscore if you need to use a reserved word as an instruction name (e.g., _import becomes import)

Example: Complete Instruction Implementation

Here's a complete example showing how the open instruction was implemented:

@Instruction
public TransformationSequence open(IAbstractInput input) throws SimulationException {
    // Read null-terminated string from memory
    int address = (int) input.get(simulator).intValue();
    StringBuilder sb = new StringBuilder();
    int index = 0;
    long current = simulator.getMemory().read(address).intValue();
    while (current != 0) {
        sb.append((char) current);
        index++;
        current = simulator.getMemory().read(address + index * Memory.getWordSize()).intValue();
    }
    
    // Call simulator method to perform file operation
    int fd = simulator.openFile(sb.toString());
    
    // Create transformation to set FD register
    InputOutputTransformable fdio = new InputOutputTransformable(
        simulator, 
        new RegisterInputOutput(Registers.FD)
    );
    Transformation t = fdio.transformation(new RawData(fd));
    
    return new TransformationSequence(t);
}

This instruction reads a filename from memory, opens the file via the simulator, and stores the resulting file descriptor in the $fd register—all following EzASM's transformation-based architecture.