UML Control Flow Opcodes

From MAMEDEV Wiki
Revision as of 13:18, 23 March 2018 by Cuavas (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Below is a detailed description of all the UML control flow opcodes. For general information about the UML, please see UML Architecture.

CALLC

Usage:

CALLC   function,parameter[,condition]

Codegen Shorthand:

UML_CALLC(block, function, parameter);
UML_CALLCc(block, condition, function, parameter);

Parameters:

  • function — a memory pointer to a C function of the form void cfunction(void *parameter)
  • parameter — a memory pointer that is passed as the argument to the C function
  • condition — an optional condition which is used to determine whether or not to execute the call

Flags: undefined

Description: The CALLC opcode is used to execute a C function from within the generated code. A single parameter (evaluated at compile time only) may be passed to the function. More data can be effectively passed by using the parameter to point to a communication block. Upon return from the C function, all registers retain their original values; however, all flags are left in an undefined state.

Like most control flow opcodes, CALLC can be used either unconditionally or flagged with a condition that controls whether or not the target function is called.

Example:

void cfunc_printf_string(void *parameter)
{
    printf("%s", parameter);
}

void generate_call_to_print_string(drcuml_block *block)
{
    UML_CALLC(block, cfunc_printf_string, "My test string");
}

CALLH

Usage:

CALLH   handle[,condition]

Codegen Shorthand:

UML_CALLH(block, handle);
UML_CALLHc(block, condition, handle);

Parameters:

  • handle — a memory pointer to a previously-allocated code handle
  • condition — an optional condition which is used to determine whether or not to execute the call

Flags: undefined

Description: The CALLH function makes a subroutine call to the code referenced by the given handle. Note that although the handle must have already been allocated, it is permitted to pass a handle which has not yet been populated by code. The back-end code generator must support both cases, though it will generally output less efficient code if the handle is not yet populated (since it must fetch the address at runtime from the handle).

Like most control flow opcodes, CALLH can be used either unconditionally or flagged with a condition that controls whether or not the target function is called.

Example:

drcuml_state drcuml;
uml::code_handle *mysubroutine;

void generate_subroutine_code(drcuml_state *drcuml)
{
    drcuml_block *block;
    jmp_buf errorbuf;

    /* allocate a handle */
    mysubroutine = drcuml.handle_alloc(drcuml, "my_subroutine_name");

    /* handle a fatal error during codegen */
    if (setjmp(errorbuf) != 0)
        fatalerror("Ran out of cache space generating subroutine!");
 
    /* generate a simple subroutine */
    block = drcuml_block_begin(drcuml, 10, &errorbuf);
    UML_HANDLE(block, *mysubroutine);
    UML_RET(block);

    /* complete codegen */
    drcuml_block_end(block);
}

void generate_call_to_subroutine(drcuml_block *block)
{
    UML_CALLH(block, *mysubroutine);
}

COMMENT

Usage:

COMMENT  string

Codegen Shorthand:

UML_COMMENT(block, string);

Parameters:

  • string — a memory pointer to character string

Flags: unaffected

Description: The COMMENT opcode exists to provide a means of documenting the UML code. Comments are generally ignored completely by the back-end and typically generate no code. However, they do show up in the disassembly of UML code, and may optionally propogate to disassembly of generated code as well.

Example:

void generate_comment(drcuml_block *block)
{
    UML_COMMENT(block, "This is a comment for disassembly");
}

DEBUG

Usage:

DEBUG   pc

Codegen Shorthand:

UML_DEBUG(block, PTYPE(pc));

Parameters:

  • pc — a 32-bit integer register, memory location, map variable, or immediate

Flags: undefined

Description: The DEBUG opcode indicates that, if the debugger is present, it should be updated with the provided pc as the current instruction. To properly support the built-in debugger, this opcode should be present before the execution of each instruction. Before issuing a DEBUG opcode, be sure to flush any cached state (including the PC) to its target memory location so that the debugger can properly examine it or modify it. Upon completion of this opcode, all registers retain their original values; however, all flags are left in an undefined state.

Example:

offs_t currentpc;

void generate_call_to_debugger(drcuml_block *block)
{
    UML_DEBUG(block, mem(&currentpc));
}

EXH

Usage:

EXH     handle,parameter[,cond]

Codegen Shorthand:

UML_EXH(block, handle, PTYPE(parameter));
UML_EXHc(block, condition, handle, PTYPE(parameter));

Parameters:

  • handle — a memory pointer to a previously-allocated code handle
  • parameter — a 32-bit integer register, memory location, map variable, or immediate
  • condition — an optional condition which is used to determine whether or not to generate the exception

Flags: undefined

Description: The EXH function generates an exception, which is effectively a subroutine call with a special parameter. The code to call is referenced by the given handle. As with the CALLH opcode, the handle must have already been allocated, though again it is permitted to pass a handle which has not yet been populated by code. The back-end code generator must support both cases, though it will generally output less efficient code if the handle is not yet populated (since it must fetch the address at runtime from the handle). The parameter is stored in a special internal register called EXP and can be retrieved via the GETEXP opcode.

Like most control flow opcodes, EXH can be used either unconditionally or flagged with a condition that controls whether or not the exception is generated.

Example:

drcuml_state drcuml;
uml::code_handle *myexceptionhandler;

void generate_exception_handler(drcuml_state *drcuml)
{
    drcuml_block *block;
    jmp_buf errorbuf;

    /* allocate a handle */
    myexceptionhandler = drcuml.handle_alloc(drcuml, "handle_exception");

    /* handle a fatal error during codegen */
    if (setjmp(errorbuf) != 0)
        fatalerror("Ran out of cache space generating exception handler!");
 
    /* generate a simple exception handler */
    block = drcuml_block_begin(drcuml, 10, &errorbuf);
    UML_HANDLE(block, *myexceptionhandler);
    UML_RET(block);

    /* complete codegen */
    drcuml_block_end(block);
}

void generate_exception_if_non_zero(drcuml_block *block, int parameter)
{
    UML_EXHc(block, COND_NZ, *myexceptionhandler, parameter);
}

EXIT

Usage:

EXIT    parameter[,condition]

Codegen Shorthand:

UML_EXIT(block, PTYPE(parameter));
UML_EXITc(block, condition, PTYPE(parameter));

Parameters:

  • parameter — a 32-bit integer register, memory location, map variable, or immediate
  • condition — an optional condition which is used to determine whether or not to exit

Flags: undefined

Description: The EXIT opcode immediately exits from the generated code and returns control back to the back-end, which ultimately returns control to the dynamic recompiler. The exit can be performed even from within a subroutine or exception handler. The parameter is surfaced as the return value of the execute function.

Like most control flow opcodes, EXIT can be used either unconditionally or flagged with a condition that controls whether or not the exit occurs.

Example:

void exit_with_return_code_in_i0(drcuml_block *block)
{
    UML_EXIT(block, ireg(0));
}

HANDLE

Usage:

HANDLE  handle

Codegen Shorthand:

UML_HANDLE(block, handle);

Parameters:

  • handle — a memory pointer to previously-allocated handle

Flags: unaffected

Description: The HANDLE opcode connects the current code position to the specified handle. The handle must have been previously explicitly allocated. By definition only one piece of code can connect itself to a handle; attempts to attach a new piece of code to an existing handle are considered errors and will assert in debug builds.

Example:

void attach_code_to_handle(drcuml_block *block, uml::code_handle *handle)
{
    UML_HANDLE(block, *handle);
}

HASH

Usage:

HASH    mode, pc

Codegen Shorthand:

UML_HASH(block, mode, pc);

Parameters:

  • mode — a 32-bit immediate value
  • pc — a 32-bit immediate value

Flags: unaffected

Description: The HASH opcode connects the current code position to the back-end's hash table. The back-end is free to implement whatever code lookup mechanism it desires, though a standard one is provided by the back-end utilities module. The available number of modes is specified at dynamic recompiler initialization time; it is an error to specify mode value beyond the number of modes requested at that time.

Note that unlike a handle, a hash for a given mode/pc combination can be replaced at any time.

Example:

void attach_code_to_mode_and_pc(drcuml_block *block, UINT32 mode, UINT32 pc)
{
    UML_HASH(block, mode, pc);
}

HASHJMP

Usage:

HASHJMP mode,pc,handle

Codegen Shorthand:

UML_HASHJMP(block, PTYPE(mode), PTYPE(pc), handle);

Parameters:

  • mode — a 32-bit integer register, memory location, map variable, or immediate
  • pc — a 32-bit integer register, memory location, map variable, or immediate
  • handle — memory pointer to allocated handle

Flags: undefined

Description: The HASHJMP opcode transfers control to a block of code that has previously been connected to the provided mode/pc pair. If no code has been registered, an exception is generated by setting the exception parameter EXP to pc and calling the given handle. The expected response to this is to return to the dynamic recompiler and request that it generate new code for the provided mode/pc pair.

Example:

uml::code_handle *nocode_exception_handler;
UINT32 current_mode;

void jump_to_code_at_pc_in_current_mode(drcuml_block *block, UINT32 pc)
{
    UML_HASHJMP(block, mem(&currentmode), pc, *nocode_exception_handler);
}

JMP

Usage:

JMP     label[,cond]

Codegen Shorthand:

UML_JMP(block, label);
UML_JMPc(block, condition, label);

Parameters:

  • label — a 32-bit immediate value
  • condition — an optional condition which is used to determine whether or not to jump

Flags: unaffected

Description: The JMP opcode transfers control to another location within the current block. The label parameter specifies a 32-bit integer label that serves as the jump target. Within the block, a LABEL opcode must be present to signify what code the label refers to. In debug builds, attempts to jump to a non-existent label will assert at back-end code generation time (when all the labels are resolved).

Like most control flow opcodes, JMP can be used either unconditionally or flagged with a condition that controls whether or not the jump occurs.

Example:

void conditional_jump_around_code(drcuml_block *block)
{
    uml::code_label curlabel = 1;
    uml::code_label skip;

    UML_JMPc(block, COND_NZ, skip = curlabel++);
    <other code here>
    UML_LABEL(skip);
}

LABEL

Usage:

LABEL   label

Codegen Shorthand:

UML_LABEL(block, label);

Parameters:

  • label — 32-bit immediate value

Flags: unaffected

Description: The LABEL opcode connects the current code position to a block-local label with the value of label. Only one label per block of UML can claim the same label value, so the dynamic recompiler must set up some conventions to ensure that each label has a unique 32-bit identifier.

Example:

void attach_code_to_label(drcuml_block *block, uml::code_label label)
{
    UML_LABEL(block, label);
}

MAPVAR

Usage:

MAPVAR  mapvar,value

Codegen Shorthand:

UML_MAPVAR(block, MVAR(mapvar), value);

Parameters:

  • mapvar — a map variable
  • value — a 32-bit immediate value

Flags: unaffected

Description: The MAPVAR opcode sets the specified map variable mapvar to a new value value starting at the current position within the code. Map variables are encoded into the instruction stream and can be retrieved from within a subroutine or exception handler via the RECOVER opcode. At the start of a block, all map variables default to 0, and hold that value until a MAPVAR opcode overrides it, at which point all subsequent code will take on the new value.

Note that because the RECOVER opcode always operates on the outermost return address, it does not make sense to use MAPVAR within a subroutine.

Example:

void generate_some_code(drcuml_block *block)
{
    UML_MAPVAR(block, mvar(0), 1);           /* we are in section 1 of the code */
    <section 1 code here>
    UML_MAPVAR(block, mvar(0), 2);           /* now we are in section 2 of the code */
    <section 2 code here>
}

RECOVER

Usage:

RECOVER dest,mapvar

Codegen Shorthand:

UML_RECOVER(block, PTYPE(dest), MVAR(mapvar));

Parameters:

  • dest — a 32-bit integer register or memory location
  • mapvar — a map variable

Flags: undefined

Description: The RECOVER opcode retrieves the value of the given mapvar and stores it in dest. It does this by fetching the outermost return address from the stack and using that value to find the value specified by the most recent MAPVAR opcode that preceded the point where the subroutine call or exception generation happened. Because of this behavior, it only makes sense to use RECOVER from within a subroutine or exception handler; using it outside of this context will produce undefined results.

Example:

void generate_recover_code_section_to_i1(drcuml_block *block)
{
    UML_RECOVER(block, ireg(0), mvar(0));
}

RET

Usage:

RET     [cond]

Codegen Shorthand:

UML_RET(block);
UML_RETc(block, condition);

Parameters:

  • condition — an optional condition which is used to determine whether or not to return

Flags: undefined

Description: Returns control from a subroutine call or exception handler to the instruction following the CALLH or EXH opcode.

Like most control flow opcodes, RET can be used either unconditionally or flagged with a condition that controls whether or not the return occurs.

Example:

void generate_return_from_function_if_carry(drcuml_block *block)
{
    UML_RETc(block, COND_C);
}