For MiniScript2, opcodes must be added very specifically so that all operations work correctly from assembly to execution.
To add a new opcode, the following must be implemented:
- cs/Bytecode.cs: New opcodes must be added to
Opcodeenum. They must follow the existing format, withrindicating a register,ka constant table lookup, andian immediate value. Also, matching entries must be placed inBytecodeUtil.ToMnemonic()andBytecodeUtil.FromMnemonic(). - cpp/core/dispatch_macros.h: The raw opcodes must be added to the list of macros in
VM_OpCodes(X). - cs/Assembler.cs: The assembly version of the opcodes must be implemented in
Assembler.AddLine(). - cs/Disassembler.cs: The raw opcodes and assembly opcodes must be matched together in both
Disassembler.AssemOp()andDisassembler.ToString(). - cs/VM.cs: Implementation of the raw opcodes must be completed in
VM.Execute() - VM_DESIGN.md: The new opcodes should be documented.
Opcodes should be implemented in the same order across all files, as best as possible. If your new opcode comes immediately after the JUMP opcode in cs/Bytecode.cs, then it should be implemented after JUMP in cs/VM.cs, for example.
Sometimes you'll need to add an additional encoding or decoding operation in cs/Bytecode.cs.
LOAD is an assembly opcode. Depending on how it'll be used, it will be translated into one of the following raw opcodes: LOAD_rA_rB, LOAD_rA_iBC, or LOAD_rA_kBC.
Let's say we want to implement an INC opcode that increments a register's value by 1. The assembly opcode would be INC, but the raw opcode interpreted by the VM would be INC_rA. Let's assume we only know how to add support for integers just now, and not for any other data types. We can begin to add the opcode like this:
Inside Opcode, we would add INC_rA somewhere near the math opcodes, perhaps like this:
ADD_rA_rB_rC,
SUB_rA_rB_rC,
MULT_rA_rB_rC,
DIV_rA_rB_rC,
MOD_rA_rB_rC,
INC_rA, // Our added opcode
JUMP_iABC,Inside BytecodeUtil.ToMnemonic(), we add the following:
case Opcode.INC_rA: return "INC_rA";Inside BytecodeUtil.FromMnemonic(), we add the following:
if (s == "INC_rA") return Opcode.INC_rA;We can implement the assembly side like this:
else if (mnemonic == "INC") {
if (parts.Count != 2) return Error("Syntax error", mnemonic, line);
Byte reg = ParseRegister(parts[1]);
instruction = BytecodeUtil.INS_A(Opcode.INC_rA, reg);
}Note: If the encoding function BytecodeUtil.INS_A doesn't exist, then we'll need to go back to cs/Bytecode.cs and add it.
We'll add the following to Disassembler.AssemOp():
case Opcode.INC_rA: return "INC";We'll also add the following to Disassembler.ToString():
case Opcode.INC_rA:
return StringUtils.Format("{0} r{1}",
mnemonic,
(Int32)BytecodeUtil.Au(instruction));We'll add the following to VM.Execute():
case Opcode.INC_rA: { // CPP: VM_CASE(INC_rA) {
// R[A]++
Byte a = BytecodeUtil.Au(instruction);
stack[baseIndex + a] = value_add(stack[baseIndex + a], val_one);
// We assume R[A] is an integer.
// TODO: Add support for other data types, like strings.
break; // CPP: VM_NEXT();
}We'll add the following to the VM_OpCodes macro, in the same place we added it in Opcode in cs/Bytecode.cs:
X(INC_rA) \We'll add the following to the opcode documentation table:
| INC_rA | R[A]++ |