#include #include #include #include #include #include "utils/Functions.h" #include "vm/Operation.h" #include "vm/Script.h" void sError(Script* sc, const char* format, ...) { va_list args; va_start(args, format); vsnprintf(sc->error, SCRIPT_ERROR_SIZE, format, args); va_end(args); } static bool sRead(Script* sc, void* buffer, int length) { if(sc->readIndex + length > sc->code->length) { sError(sc, "cannot read expected %d bytes of data from bytecode"); return false; } memcpy(buffer, sc->code->code + sc->readIndex, length); sc->readIndex += length; return true; } static Operation sReadOperation(Script* sc) { unsigned char c; if(sRead(sc, &c, 1)) { return c; } return OP_NOTHING; } static void* sReserve(Script* sc, int length) { if(length < 0) { sError(sc, "invalid reserve length %d", length); return NULL; } else if(sc->stackIndex + length > SCRIPT_STACK_SIZE) { sError(sc, "stack overflow"); return NULL; } void* p = sc->stack + sc->stackIndex; sc->stackIndex += length; return p; } static bool sPush(Script* sc, const void* data, int length) { void* p = sReserve(sc, length); if(p != NULL) { memcpy(p, data, length); return true; } return false; } static const void* sFree(Script* sc, int length) { if(sc->stackIndex < length) { sError(sc, "stack underflow"); return NULL; } sc->stackIndex -= length; return sc->stack + sc->stackIndex; } static bool sPop(Script* sc, void* data, int length) { const void* p = sFree(sc, length); if(p != NULL) { memcpy(data, p, length); return true; } return false; } static bool sPeek(Script* sc, void* data, int length) { if(sc->stackIndex < length) { sError(sc, "stack underflow"); return false; } memcpy(data, sc->stack + (sc->stackIndex - length), length); return true; } #define POP_PUSH(type, Type) \ bool sPop##Type(Script* sc, type* value) { \ return sPop(sc, value, sizeof(type)); \ } \ bool sPush##Type(Script* sc, type value) { \ return sPush(sc, &value, sizeof(type)); \ } #define READ_POP_PUSH(type, Type) \ static bool sRead##Type(Script* sc, type* i) { \ return sRead(sc, i, sizeof(type)); \ } \ POP_PUSH(type, Type) #define PUSH_CONSTANT(type, Type) \ { \ type value; \ if(sRead##Type(sc, &value)) { \ sPush##Type(sc, value); \ } \ } #define ZERO_CHECK(name) \ if(values[0] == 0) { \ sError(sc, name " by 0"); \ return; \ } #define OP_BASE(type, Type, RType, op, check) \ { \ type values[2]; \ if(sPop##Type(sc, values) && sPop##Type(sc, values + 1)) { \ check; \ sPush##RType(sc, values[1] op values[0]); \ } \ } #define CHECKED_NUMBER_OP(type, Type, op, check) \ OP_BASE(type, Type, Type, op, check) #define NUMBER_OP(type, Type, op) CHECKED_NUMBER_OP(type, Type, op, ) #define BOOL_OP(type, Type, op) OP_BASE(type, Type, Bool, op, ) #define DIVISION(type, Type) \ CHECKED_NUMBER_OP(type, Type, /, ZERO_CHECK("division")); #define MODULE(type, Type) \ CHECKED_NUMBER_OP(type, Type, %, ZERO_CHECK("module")); READ_POP_PUSH(int32, Int32) READ_POP_PUSH(int64, Int64) READ_POP_PUSH(float, Float) POP_PUSH(bool, Bool) bool sPopPointer(Script* sc, Pointer* value) { return sPop(sc, value, sizeof(Pointer)); } bool sPushPointer(Script* sc, Pointer* value) { return sPush(sc, value, sizeof(Pointer)); } bool sPushNullPointer(Script* sc) { Pointer p = {-1, -1}; return sPushPointer(sc, &p); } #define INVERT_SIGN(type, Type) \ { \ type value = 0; \ if(sPop##Type(sc, &value)) { \ sPush##Type(sc, -value); \ } \ } static void sReserveBytes(Script* sc) { int32 bytes = 0; int32 offset = 0; if(sReadInt32(sc, &bytes) && sReadInt32(sc, &offset)) { int32 oldIndex = sc->stackVarIndex; sc->stackVarIndex = sc->stackIndex - offset; sReserve(sc, bytes - offset); sPushInt32(sc, oldIndex); } } static void sGlobalReserveBytes(Script* sc) { int32 bytes = 0; if(sReadInt32(sc, &bytes)) { sc->stackVarIndex = sc->stackIndex; if(bytes >= 0) { sReserve(sc, bytes); } else { sc->stackIndex += bytes; if(sc->stackIndex < 0) { sError(sc, "invalid global free %d", bytes); } } } } static int32 sGetTextLength(Script* sc, Pointer* p) { const int SIZE = sizeof(int32); int sizeAddress = -p->array; if(sizeAddress < 0 || sizeAddress + SIZE >= sc->code->length) { sError(sc, "invalid code array length address %d", sizeAddress); return -1; } int32 size; memcpy(&size, sc->code->code + sizeAddress, SIZE); return size; } static void* sCheckTextAddress(Script* sc, Pointer* p, int length) { const int SIZE = sizeof(int32); int address = p->offset + SIZE; if(address + length > sc->code->length) { sError(sc, "address %d is out of code bounds", address); return NULL; } int32 size = sGetTextLength(sc, p); if(size == -1) { return NULL; } int startAddress = -p->array + SIZE; int endAddress = startAddress + size * SIZE; if(address + length > endAddress || address < startAddress) { sError(sc, "invalid text address %d", (address - startAddress) / SIZE); return NULL; } return sc->code->code + address; } void* sCheckAddress(Script* sc, Pointer* p, int length) { if(p->array >= 0) { SnuviArray* a = asGet(&sc->arrays, p->array); if(a == NULL) { sError(sc, "invalid heap pointer"); return NULL; } else if(p->offset < 0 || p->offset >= a->size) { sError(sc, "address %d is out of array bounds", p->offset); return NULL; } return ((char*)a->data) + p->offset; } else if(p->array == -1) { if(p->offset < 0 || p->offset + length > sc->stackIndex) { sError(sc, "address %d is out of stack bounds", p->offset); return NULL; } return sc->stack + p->offset; } return sCheckTextAddress(sc, p, length); } static void sNot(Script* sc) { bool value = false; if(sPopBool(sc, &value)) { sPushBool(sc, !value); } } static void sBitNotInt32(Script* sc) { int32 value = 0; if(sPopInt32(sc, &value)) { sPushInt32(sc, ~value); } } static void sBitNotInt64(Script* sc) { int64 value = 0; if(sPopInt64(sc, &value)) { sPushInt64(sc, ~value); } } static void sLine(Script* sc) { sRead(sc, &sc->line, 2); } static void sGoTo(Script* sc) { int32 gotoIndex; if(sReadInt32(sc, &gotoIndex)) { sc->readIndex = gotoIndex; } } static void sGoSub(Script* sc) { int32 gotoIndex; int32 offset; if(sReadInt32(sc, &gotoIndex) && sReadInt32(sc, &offset)) { Pointer p = {.array = -1, .offset = sc->stackIndex - offset - sizeof(int32)}; void* dest = sCheckAddress(sc, &p, sizeof(int32)); if(dest != NULL) { memcpy(dest, &sc->readIndex, sizeof(int32)); sc->readIndex = gotoIndex; } } } static void sReturn(Script* sc) { int32 bytes = 0; int32 varIndex = 0; if(sReadInt32(sc, &bytes) && sPopInt32(sc, &varIndex)) { sc->stackVarIndex = varIndex; sFree(sc, bytes); if(!sPopInt32(sc, &sc->readIndex) || sc->readIndex < 0) { sError(sc, "read index is corrupt"); } } } static void sReturnPointer(Script* sc) { Pointer p; if(sPopPointer(sc, &p)) { sReturn(sc); sPushPointer(sc, &p); } } #define RETURN(type, Type) \ { \ type value; \ if(sPop##Type(sc, &value)) { \ sReturn(sc); \ sPush##Type(sc, value); \ } \ } static void sIfGoTo(Script* sc) { int32 gotoIndex = 0; bool value = false; if(sReadInt32(sc, &gotoIndex) && sPopBool(sc, &value) && !value) { sc->readIndex = gotoIndex; } } static void sPeekFalseGoTo(Script* sc) { int32 gotoIndex = 0; bool value = false; if(sReadInt32(sc, &gotoIndex) && sPeek(sc, &value, sizeof(bool)) && !value) { sc->readIndex = gotoIndex; } } static void sPeekTrueGoTo(Script* sc) { int32 gotoIndex = 0; bool value = false; if(sReadInt32(sc, &gotoIndex) && sPeek(sc, &value, sizeof(bool)) && value) { sc->readIndex = gotoIndex; } } static void sNewArray(Script* sc) { int32 length = 0; int32 size = 0; if(sReadInt32(sc, &size) && sPopInt32(sc, &length)) { Pointer p = {.array = asAllocate(&sc->arrays, size, length), .offset = 0}; if(p.array == -1) { sError(sc, "out of memory"); } else if(p.array == -2) { sError(sc, "bad allocation"); } else { sPushPointer(sc, &p); } } } static void sDeleteArray(Script* sc) { Pointer p; if(sPopPointer(sc, &p)) { if(p.offset != 0) { sError(sc, "delete of array with offset: %d", p.offset); return; } SnuviArray* a = asGet(&sc->arrays, p.array); if(a == NULL) { sError(sc, "delete of invalid array"); return; } asDeleteArray(&sc->arrays, a, p.array); } } bool sGetPointerLength(Script* sc, Pointer* p, int32* length) { if(p->array == -1) { *length = p->offset >= 0; return false; } else if(p->array <= -1) { int32 l = sGetTextLength(sc, p); if(l != -1) { *length = l; return false; } return true; } SnuviArray* a = asGet(&sc->arrays, p->array); if(a == NULL) { sError(sc, "invalid heap pointer %d", p->array); return true; } *length = a->length; return false; } static void sLength(Script* sc) { Pointer p; int32 length; if(sPopPointer(sc, &p) && !sGetPointerLength(sc, &p, &length)) { sPushInt32(sc, length); } } static void sDereference(Script* sc) { int32 address = 0; if(sReadInt32(sc, &address)) { Pointer p = {.array = -1, .offset = address + sc->stackVarIndex}; sPushPointer(sc, &p); } } static void sGlobalDereference(Script* sc) { int32 address = 0; if(sReadInt32(sc, &address)) { Pointer p = {.array = -1, .offset = address}; sPushPointer(sc, &p); } } static void sLoad(Script* sc, int length) { Pointer p; if(sPopPointer(sc, &p)) { void* src = sCheckAddress(sc, &p, length); if(src != NULL) { sPush(sc, src, length); } } } static void sDuplicateReference(Script* sc) { Pointer p; if(sPeek(sc, &p, sizeof(Pointer))) { sPushPointer(sc, &p); } } static void sAddReference(Script* sc) { int32 size = 0; int32 add = 0; Pointer p; if(sReadInt32(sc, &size) && sPopInt32(sc, &add) && sPopPointer(sc, &p)) { p.offset += add * size; sPushPointer(sc, &p); } } static void sLoadSize(Script* sc) { int32 size = 0; Pointer p; if(sReadInt32(sc, &size) && sPopPointer(sc, &p)) { void* src = sCheckAddress(sc, &p, size); if(src != NULL) { sPush(sc, src, size); } } } static void sStore(Script* sc, int length) { int index = sc->stackIndex - sizeof(Pointer) - length; if(index < 0) { sError(sc, "stack underflow"); return; } Pointer p; memcpy(&p, sc->stack + index, sizeof(Pointer)); void* dest = sCheckAddress(sc, &p, length); if(dest != NULL) { sPop(sc, dest, length); sc->stackIndex -= sizeof(Pointer); } } static void sEqualPointer(Script* sc, bool wanted) { Pointer a; Pointer b; if(sPopPointer(sc, &a) && sPopPointer(sc, &b)) { sPushBool(sc, (a.array == b.array && a.offset == b.offset) == wanted); } } static void sCall(Script* sc) { int32 function = 0; if(sReadInt32(sc, &function) && gfsCall(sc, function)) { sError(sc, "invalid function call"); } } static void sPushText(Script* sc) { int address = sc->readIndex; int32 length; if(sReadInt32(sc, &length)) { Pointer p = {.array = -address, .offset = address}; sPushPointer(sc, &p); sc->readIndex += sizeof(int32) * length; } } #define CHANGE_OP(type, op) \ { \ char c = 0; \ Pointer p; \ if(sRead(sc, &c, sizeof(char)) && sPopPointer(sc, &p)) { \ void* data = sCheckAddress(sc, &p, sizeof(type)); \ if(data != NULL) { \ type current; \ memcpy(¤t, data, sizeof(type)); \ op \ } \ } \ } #define PUSH_PRE_CHANGE(Type, type) \ CHANGE_OP(type, current += c; sPush##Type(sc, current); \ memcpy(data, ¤t, sizeof(type));) #define PUSH_POST_CHANGE(Type, type) \ CHANGE_OP(type, sPush##Type(sc, current); current += c; \ memcpy(data, ¤t, sizeof(type));) #define CHANGE(type) \ CHANGE_OP(type, current += c; memcpy(data, ¤t, sizeof(type));) #define CAST(From, from, To) \ { \ from value; \ if(sPop##From(sc, &value)) { \ sPush##To(sc, value); \ } \ } #define CASE_CAST(TYPE1, Type1, type1, TYPE2, Type2, type2) \ case OP_##TYPE1##_TO_##TYPE2: CAST(Type1, type1, Type2); break; \ case OP_##TYPE2##_TO_##TYPE1: \ CAST(Type2, type2, Type1); \ break; #define CASE_CHANGE(TYPE, Type, type) \ case OP_PUSH_PRE_CHANGE_##TYPE: PUSH_PRE_CHANGE(Type, type); break; \ case OP_PUSH_POST_CHANGE_##TYPE: PUSH_POST_CHANGE(Type, type); break; \ case OP_CHANGE_##TYPE: \ CHANGE(type); \ break; #define CASE_INTEGRAL_OP(name, op) \ case OP_##name##_INT32: NUMBER_OP(int32, Int32, op); break; \ case OP_##name##_INT64: \ NUMBER_OP(int64, Int64, op); \ break; #define CASE_NUMBER_OP(name, op) \ CASE_INTEGRAL_OP(name, op) \ case OP_##name##_FLOAT: \ NUMBER_OP(float, Float, op); \ break; #define CASE_BOOL_OP(name, op) \ case OP_##name##_INT32: BOOL_OP(int32, Int32, op); break; \ case OP_##name##_INT64: BOOL_OP(int64, Int64, op); break; \ case OP_##name##_FLOAT: \ BOOL_OP(float, Float, op); \ break; #define CASE_TYPE(TYPE, Type, type) \ case OP_STORE_##TYPE: sStore(sc, sizeof(type)); break; \ case OP_RETURN_##TYPE: RETURN(type, Type); break; \ case OP_EQUAL_##TYPE: BOOL_OP(type, Type, ==); break; \ case OP_NOT_EQUAL_##TYPE: BOOL_OP(type, Type, !=); break; \ case OP_LOAD_##TYPE: sLoad(sc, sizeof(type)); break; static void sConsumeInstruction(Script* sc) { switch(sReadOperation(sc)) { CASE_NUMBER_OP(ADD, +); CASE_NUMBER_OP(SUB, -); CASE_NUMBER_OP(MUL, *); CASE_BOOL_OP(LESS, <); CASE_BOOL_OP(LESS_EQUAL, <=); CASE_BOOL_OP(GREATER, >); CASE_BOOL_OP(GREATER_EQUAL, >=); CASE_TYPE(INT32, Int32, int32); CASE_TYPE(INT64, Int64, int64); CASE_TYPE(BOOL, Bool, bool); CASE_TYPE(FLOAT, Float, float); CASE_CHANGE(INT32, Int32, int32); CASE_CHANGE(INT64, Int64, int64); CASE_INTEGRAL_OP(BIT_AND, &); CASE_INTEGRAL_OP(BIT_OR, |); CASE_INTEGRAL_OP(BIT_XOR, ^); CASE_INTEGRAL_OP(LEFT_SHIFT, <<); CASE_INTEGRAL_OP(RIGHT_SHIFT, >>); CASE_CAST(INT32, Int32, int32, FLOAT, Float, float); CASE_CAST(INT32, Int32, int32, INT64, Int64, int64); CASE_CAST(INT64, Int64, int64, FLOAT, Float, float); case OP_NOTHING: break; case OP_PUSH_INT32: PUSH_CONSTANT(int32, Int32); break; case OP_PUSH_INT64: PUSH_CONSTANT(int64, Int64); break; case OP_PUSH_FLOAT: PUSH_CONSTANT(float, Float); break; case OP_PUSH_TRUE: sPushBool(sc, true); break; case OP_PUSH_FALSE: sPushBool(sc, false); break; case OP_PUSH_NULLPTR: sPushNullPointer(sc); break; case OP_PUSH_TEXT: sPushText(sc); break; case OP_DIV_INT32: DIVISION(int32, Int32); break; case OP_DIV_INT64: DIVISION(int64, Int64); break; case OP_DIV_FLOAT: DIVISION(float, Float); break; case OP_MOD_INT32: MODULE(int32, Int32); break; case OP_MOD_INT64: MODULE(int64, Int64); break; case OP_INVERT_SIGN_INT32: INVERT_SIGN(int32, Int32); break; case OP_INVERT_SIGN_INT64: INVERT_SIGN(int64, Int64); break; case OP_INVERT_SIGN_FLOAT: INVERT_SIGN(float, Float); break; case OP_NOT: sNot(sc); break; case OP_AND: BOOL_OP(bool, Bool, &&); break; case OP_OR: BOOL_OP(bool, Bool, ||); break; case OP_BIT_NOT_INT32: sBitNotInt32(sc); break; case OP_BIT_NOT_INT64: sBitNotInt64(sc); break; case OP_LINE: sLine(sc); break; case OP_GOTO: sGoTo(sc); break; case OP_IF_GOTO: sIfGoTo(sc); break; case OP_PEEK_FALSE_GOTO: sPeekFalseGoTo(sc); break; case OP_PEEK_TRUE_GOTO: sPeekTrueGoTo(sc); break; case OP_GOSUB: sGoSub(sc); break; case OP_RETURN: sReturn(sc); break; case OP_RETURN_POINTER: sReturnPointer(sc); break; case OP_RESERVE: sReserveBytes(sc); break; case OP_GRESERVE: sGlobalReserveBytes(sc); break; case OP_DEREFERENCE_VAR: sDereference(sc); break; case OP_DEREFERENCE_GVAR: sGlobalDereference(sc); break; case OP_REFERENCE: sLoad(sc, sizeof(Pointer)); break; case OP_DUPLICATE_REFERENCE: sDuplicateReference(sc); break; case OP_ADD_REFERENCE: sAddReference(sc); break; case OP_LOAD: sLoadSize(sc); break; case OP_NEW: sNewArray(sc); break; case OP_DELETE: sDeleteArray(sc); break; case OP_LENGTH: sLength(sc); break; case OP_STORE_POINTER: sStore(sc, sizeof(Pointer)); break; case OP_EQUAL_POINTER: sEqualPointer(sc, true); break; case OP_NOT_EQUAL_POINTER: sEqualPointer(sc, false); break; case OP_CALL: sCall(sc); break; } } static bool sHasData(Script* sc) { return sc->readIndex < sc->code->length; } Script* sInit(ByteCode* code) { Script* sc = malloc(sizeof(Script)); sc->error[0] = '\0'; sc->code = code; sc->readIndex = 0; sc->stackIndex = 0; sc->stackVarIndex = 0; sc->line = 0; asInit(&sc->arrays); return sc; } void sDelete(Script* sc) { bcDelete(sc->code); asDelete(&sc->arrays); free(sc); } void sRun(Script* sc) { while(sHasData(sc)) { sConsumeInstruction(sc); if(sc->error[0] != '\0') { return; } } }