/*
* Copyright (c) 2024 Ross Cunniff
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
// OADL opcodes
enum {
// Miscellaneous group
OP_NOP = 0x00,
OP_POP, // val pop
OP_DUP, // val dup => val val
OP_EXCH, // v1 v2 exch => v2 v1
OP_UNUSED_0x04, // unused opcode #0x04
OP_INC, // v0 => v0 + 1 (without promotion)
OP_DEC, // v0 => v0 - 1 (without promotion)
// Unused: 0x07
// Arithmetic
OP_ADD = 0x08, // op1 op2 add => (op1 + op2)
OP_SUB, // op1 op2 sub => (op1 - op2)
OP_MUL, // op1 op2 mul => (op1 * op2)
OP_DIV, // op1 op2 div => (op1 / op2)
OP_MOD, // op1 op2 mod => (op1 % op2)
OP_NEG, // op1 neg => (-op1)
OP_POW, // op1 op2 pow => (op1 ** op2)
// Unused: 0x0F
// Boolean/bit
OP_AND = 0x10, // op1 op2 and => (op1 & op2)
OP_OR, // op1 op2 or => (op1 | op2)
OP_XOR, // op1 op2 xor => (op1 ^ op2)
OP_NOT, // op1 not => (~op1)
OP_LOGNOT, // op1 lognot => (~op1)
OP_LSHIFT, // op1 op2 lshift => (op1 << op2)
OP_RSHIFT, // op1 op2 rshift => (op1 >> op2)
// Unused: 0x17
// Comparison
OP_LT = 0x18, // op1 op2 lt => (op1 < op2)
OP_GT, // op1 op2 gt => (op1 > op2)
OP_LE, // op1 op2 le => (op1 <= op2)
OP_GE, // op1 op2 ge => (op1 >= op2)
// Unused: 0x1C - 0x1F
// Equivalence
OP_EQ = 0x20, // op1 op2 eq => (op1 == op2)
OP_NE, // op1 op2 ne => (op1 != op2)
OP_ARR_EQ, // op1 op2 arreq => (op1 #= op2)
// Unused: 0x21 - 0x27
// String matching
OP_MATCH = 0x28, // str patt comp MATCH => new match env
OP_ENDMATCH, // ENDMATCH => pop match env
OP_NMATCH, // NMATCH => # of matches
OP_MATCHN, // N MATCHN => (match #N)
// Unused: 0x2C - 0x2F
// Execution flow
OP_CALL = 0x30, // arg1 arg2 ... argN N addr call
OP_RET, // retval ret
OP_MCALL, // arg1 arg2 ... argN N obj public mcall
OP_JMP, // addr jmp
OP_JMPZ, // val addr jmpz
OP_LOCALS, // Allocate locals on the stack
OP_TRY, // addr try
OP_ENDTRY, // endtry
OP_THROW, // excepnum throw
OP_PROC, // PROC => current executing proc
OP_ARR_CALL, // arr addr arrcall
OP_ARR_MCALL, // arr obj public arrmcall
// Unused: 0x3C - 0x3F
// Get/set global, local, arg, index
OP_GETG = 0x40, // global getg => (*global)
OP_SETG, // global val setg
OP_SETG_TC, // type global val setg_tc
OP_GETL, // local getl => (*local)
OP_SETL, // local val setl
OP_SETL_TC, // type local val setl_tc
OP_GETA, // arg geta => (*arg)
OP_SETA, // arg val seta
OP_SETA_TC, // type arg val seta_tc
OP_GETP, // prop getp => (self.prop)
OP_SETP, // prop val setp
OP_SETP_TC, // type prop val setp_tc
OP_GETPK, // prop getpk => prop (self.prop)
OP_GETI, // arr index geti => (arr[index])
OP_GETI_M, // arr i0 i1 ... n geti_m => arr[i0,i1,...]
OP_SETI, // arr index val seti
OP_SETI_M, // arr i0 i1 ... n val seti_m
OP_GETIK, // arr index getik => arr index arr[index]
OP_GETIK_M, // arr i0 i1 ... n getik_m => arr i0 ... a[i]
OP_GETPUB, // obj pub getpub => obj.pub
OP_SETPUB, // obj pub val setpub
OP_GETPUBK, // obj pub getpubk => obj pub obj.pub
OP_SELF, // self => self
OP_LOOPINIT, // vbase expr num ishash loopinit => stop?
OP_LOOPINCR, // vbase num ishash loopincr => stop?
OP_GETI_X, // arr index geti_x => arr#[index]
OP_GETI_XK, // arr index geti_xk => arr index arr#[index]
OP_SETI_X, // arr index val seti_x
OP_SETSUBR, // arr s0 e0 s1 e1 ... n val setsubr
OP_GETSUB_K, // arr s0 e0 ... n getsub_k => arr s0 .. arr[..]
OP_ARGCHECK, // arg type argcheck
// Unused: 0x5F - 0x5F
// debugging ops
OP_BREAKPOINT = 0x60, // Break and exec
OP_DEBUG_POP, // Pop debug name stack
OP_LINE, // linenum line
OP_DEBUG_PRINT, // var DEBUG_PRINT
OP_BREAK_HALT, // Break and halt
// Unused: 0x65 - 0x6F
// Push instructions - 0 extra bytes
OP_NIL = 0x70, // Push nil - 0xFFFFFFFF
OP_FLT_0, // Push 0.0
OP_INT_0, // Push 0
OP_DBL_0, // Push 0.d (only OADL64)
OP_I64_0, // Push 0L (only OADL64)
OP_WCH_0, // Push '\x0'L
OP_F16_0, // Push 0.0h
OP_U16_0, // Push 0us
OP_I16_0, // Push 0s
OP_U8_0, // Push 0ub
OP_I8_0, // Push 0b
OP_CHR_0, // Push '\x0'
OP_FALSE, // Push false
OP_TRUE, // Push true
OP_ARG_0, // Push address of arg 0
// Unused: 0x7F
// Unused: 0x80 - 0x8F
// Push instructions - 1 extra byte
OP_INT_8 = 0x90, // Push -128 to 127
OP_I64_8, // Push -128l to 127l (OADL64)
OP_WCH_8, // Push '\x0'L to '\xFF'L
OP_U16_8, // Push 0us to 255us
OP_I16_8, // Push -128s to 127s
OP_U8_8, // Push 0ub to 255ub
OP_I8_8, // Push -128b to 127b
OP_CHR_8, // Push Char
OP_TYP_8, // Push 8-bit Type
OP_EXCP_8, // Push 8-bit Exception
OP_LOCAL_8, // next byte is local var offs
OP_ARG_8, // next byte is arg offs
OP_BUILTIN, // next byte is builtin number from builtin.h
OP_FILE_8, // Push 8-bit File
// Unused: 0x9E - 0x9F
// Multibyte instructions - 2 extra bytes
OP_INT_16 = 0xA0, // Push -32768 to 32767
OP_FLT_16, // next 2 bytes are upper bits of float
OP_I64_16, // Push -32768l to 32767l (OADL64)
OP_DBL_16, // next 2 bytes are upper bits of dbl (OADL64)
OP_WCH_16, // Push '\x0800'L to '\xFFFF'L
OP_F16_16, // Push Half
OP_U16_16, // Push 0us to 65535us
OP_I16_16, // Push -32768s to 32767s
OP_LOCAL_16, // next 2 bytes are local var offs
OP_ARG_16, // next 2 bytes are arg offs
OP_DEBUG_PUSH, // next 2 bytes are unique ID
// Unused: 0xAB - 0xAF
// Multibyte instructions - various extra bytes
OP_FLT_24 = 0xB0, // next 3 bytes are upper 3 of float
OP_DBL_24, // next 3 bytes are upper 3 of dbl (OADL64)
OP_WCH_24, // Push '\x080000'L to '\xFFFFFF'L
OP_ADDRESS, // next 3 bytes are code address offset
// Unused: 0xB4 - 0xB7 // next 3 bytes...
OP_PARENT = 0xB8, // next 4 bytes are parent object
OP_DBL_32, // next 4 bytes are upper 4 of Double (OADL64)
OP_I64_32, // next 4 bytes are lower 4 of Long (OADL64)
OP_DEBUG_VAR, // next 4 bytes are name for debugging
OP_FILE, // next 4 bytes are array ID of filename
OP_DEBUG_USE_NS, // next 4 bytes are namespace name ID
// Unused: 0xBE // next 4 bytes...
OP_VAR_64 = 0xBF, // next 8 bytes are 64-bit Var (OADL64 only)
// Multibyte instructions - 4 extra bytes
OP_PUSH_32 = 0xC0, // Lower 6 bits of opcode are type.
// next 4 bytes are value.
};
typedef uint8_t Opcode;
namespace oadl {
extern const uint8_t opcodeSizes[256];
extern const uint8_t op0types[16];
extern const uint8_t op8types[16];
extern const uint8_t op16types[16];
#if defined(OADL_DEFINE_MACH_TABLE) // [
const uint8_t opcodeSizes[256] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x90
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xA0
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 9, // 0xB0
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 0xC0
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 0xD0
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 0xE0
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 0xF0
};
const uint8_t op0types[16] = {
OADL_VT_NULL, OADL_VT_FLOAT, OADL_VT_INT, OADL_VT_DOUBLE,
OADL_VT_LONG, OADL_VT_WCHAR, OADL_VT_HALF, OADL_VT_USHORT,
OADL_VT_SHORT, OADL_VT_UBYTE, OADL_VT_BYTE, OADL_VT_CHAR,
OADL_VT_BOOL, OADL_VT_BOOL, OADL_VT_ARG, OADL_VT_NULL,
};
const uint8_t op8types[16] = {
OADL_VT_INT, OADL_VT_LONG, OADL_VT_WCHAR, OADL_VT_USHORT,
OADL_VT_SHORT, OADL_VT_UBYTE, OADL_VT_BYTE, OADL_VT_CHAR,
OADL_VT_TYPE, OADL_VT_EXCEPT, OADL_VT_LOCAL, OADL_VT_ARG,
OADL_VT_INTR, OADL_VT_FILE, OADL_VT_NULL, OADL_VT_NULL,
};
const uint8_t op16types[16] = {
OADL_VT_INT, OADL_VT_FLOAT, OADL_VT_LONG, OADL_VT_DOUBLE,
OADL_VT_WCHAR, OADL_VT_HALF, OADL_VT_USHORT, OADL_VT_SHORT,
OADL_VT_LOCAL, OADL_VT_ARG, OADL_VT_NULL, OADL_VT_NULL,
OADL_VT_NULL, OADL_VT_NULL, OADL_VT_NULL, OADL_VT_NULL,
};
#endif // ]
// Instruction decode methods
static inline int instrCount(Opcode op)
{
return oadl::opcodeSizes[op];
}
static inline int encode(Opcode *ip, Opcode op, uint32_t a0, uint32_t a1)
{
int size = instrCount(op);
ip[0] = op;
// Note tricky fallthroughs
switch (size) {
#if defined(OADL64)
case 9 :
ip[8] = (Opcode) ((a1 >> 24) & 0xFF);
ip[7] = (Opcode) ((a1 >> 16) & 0xFF);
ip[6] = (Opcode) ((a1 >> 8) & 0xFF);
ip[5] = (Opcode) ((a1 ) & 0xFF);
// FALLTHROUGH
#endif
case 5 :
ip[4] = (Opcode) ((a0 >> 24) & 0xFF);
// FALLTHROUGH
case 4 :
ip[3] = (Opcode) ((a0 >> 16) & 0xFF);
// FALLTHROUGH
case 3 :
ip[2] = (Opcode) ((a0 >> 8) & 0xFF);
// FALLTHROUGH
case 2 :
ip[1] = (Opcode) ((a0 ) & 0xFF);
break;
}
return size;
}
static inline int decode(Opcode *ip, Opcode *op,
uint32_t *a0, uint32_t *a1)
{
Opcode b0 = ip[0];
*op = b0;
uint32_t va0 = 0, va1 = 0;
int size = instrCount(b0);
// Note tricky fallthroughs again
switch (size) {
#if defined(OADL64)
case 9 :
va1 |= ip[8] << 24;
va1 |= ip[7] << 16;
va1 |= ip[6] << 8;
va1 |= ip[5];
// FALLTHROUGH
#endif
case 5 :
// FALLTHROUGH
va0 |= ip[4] << 24;
case 4 :
// FALLTHROUGH
va0 |= ip[3] << 16;
case 3 :
// FALLTHROUGH
va0 |= ip[2] << 8;
case 2 :
// FALLTHROUGH
va0 |= ip[1];
break;
}
*a0 = va0; *a1 = va1;
return size;
}
}