869 lines
36 KiB
C++
869 lines
36 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
The code included in this file is provided under the terms of the ISC license
|
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
|
To use, copy, modify, and/or distribute this software for any purpose with or
|
|
without fee is hereby granted provided that the above copyright notice and
|
|
this permission notice appear in all copies.
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace littlefoot
|
|
{
|
|
|
|
/*
|
|
This file contains classes and definitions for executing a littlefoot
|
|
bytecode program that was created with the littlefoot compiler.
|
|
*/
|
|
|
|
#if ! (defined (LITTLEFOOT_DEBUG_TRACE) || RUNNING_ON_REAL_BLOCK_DEVICE)
|
|
#define LITTLEFOOT_DEBUG_TRACE 0
|
|
#endif
|
|
|
|
#if ! (defined (LITTLEFOOT_DUMP_PROGRAM) || RUNNING_ON_REAL_BLOCK_DEVICE)
|
|
#define LITTLEFOOT_DUMP_PROGRAM 0
|
|
#endif
|
|
|
|
using int8 = signed char;
|
|
using uint8 = unsigned char;
|
|
using int16 = signed short;
|
|
using uint16 = unsigned short;
|
|
using int32 = signed int;
|
|
using uint32 = unsigned int;
|
|
using FunctionID = int16;
|
|
|
|
#define LITTLEFOOT_OPCODES(OP, OP_INT8, OP_INT16, OP_INT32) \
|
|
OP (halt) \
|
|
OP_INT16 (jump) \
|
|
OP_INT16 (jumpIfTrue) \
|
|
OP_INT16 (jumpIfFalse) \
|
|
OP_INT16 (call) \
|
|
OP_INT8 (retVoid) \
|
|
OP_INT8 (retValue) \
|
|
OP_INT16 (callNative) \
|
|
OP (drop) \
|
|
OP_INT8 (dropMultiple) \
|
|
OP_INT8 (pushMultiple0) \
|
|
OP (push0) \
|
|
OP (push1) \
|
|
OP_INT8 (push8) \
|
|
OP_INT16 (push16) \
|
|
OP_INT32 (push32) \
|
|
OP (dup) \
|
|
OP (dupOffset_01) \
|
|
OP (dupOffset_02) \
|
|
OP (dupOffset_03) \
|
|
OP (dupOffset_04) \
|
|
OP (dupOffset_05) \
|
|
OP (dupOffset_06) \
|
|
OP (dupOffset_07) \
|
|
OP_INT8 (dupOffset) \
|
|
OP_INT16 (dupOffset16) \
|
|
OP_INT8 (dropToStack) \
|
|
OP_INT16 (dropToStack16) \
|
|
OP_INT16 (dupFromGlobal) \
|
|
OP_INT16 (dropToGlobal) \
|
|
OP (int32ToFloat) \
|
|
OP (floatToInt32) \
|
|
OP (add_int32) \
|
|
OP (add_float) \
|
|
OP (mul_int32) \
|
|
OP (mul_float) \
|
|
OP (sub_int32) \
|
|
OP (sub_float) \
|
|
OP (div_int32) \
|
|
OP (div_float) \
|
|
OP (mod_int32) \
|
|
OP (bitwiseOr) \
|
|
OP (bitwiseAnd) \
|
|
OP (bitwiseXor) \
|
|
OP (bitwiseNot) \
|
|
OP (bitShiftLeft) \
|
|
OP (bitShiftRight) \
|
|
OP (logicalOr) \
|
|
OP (logicalAnd) \
|
|
OP (logicalNot) \
|
|
OP (testZE_int32) \
|
|
OP (testNZ_int32) \
|
|
OP (testGT_int32) \
|
|
OP (testGE_int32) \
|
|
OP (testLT_int32) \
|
|
OP (testLE_int32) \
|
|
OP (testZE_float) \
|
|
OP (testNZ_float) \
|
|
OP (testGT_float) \
|
|
OP (testGE_float) \
|
|
OP (testLT_float) \
|
|
OP (testLE_float) \
|
|
OP (getHeapByte) \
|
|
OP (getHeapInt) \
|
|
OP (getHeapBits) \
|
|
OP (setHeapByte) \
|
|
OP (setHeapInt) \
|
|
|
|
enum class OpCode : uint8
|
|
{
|
|
#define LITTLEFOOT_OP(name) name,
|
|
LITTLEFOOT_OPCODES (LITTLEFOOT_OP, LITTLEFOOT_OP, LITTLEFOOT_OP, LITTLEFOOT_OP)
|
|
#undef LITTLEFOOT_OP
|
|
endOfOpcodes
|
|
};
|
|
|
|
/** Available value types */
|
|
enum class Type : uint8
|
|
{
|
|
void_ = 'v',
|
|
int_ = 'i',
|
|
bool_ = 'b',
|
|
float_ = 'f'
|
|
};
|
|
|
|
//==============================================================================
|
|
/** Defines a native function that the program can call.
|
|
|
|
@tags{Blocks}
|
|
*/
|
|
struct NativeFunction
|
|
{
|
|
using ImplementationFunction = int32 (*) (void*, const int32*);
|
|
|
|
/** Creates a NativeFunction from its signature and an implementation function.
|
|
The format of nameAndArgumentTypes is "name/[return type][arg1][arg2..]"
|
|
So for example "int foobar (float, bool)" would be "foobar/ifb"
|
|
*/
|
|
NativeFunction (const char* nameAndArgumentTypes, ImplementationFunction fn) noexcept
|
|
: nameAndArguments (nameAndArgumentTypes), function (fn),
|
|
functionID (createID (nameAndArgumentTypes)), returnType(), numArgs()
|
|
{
|
|
const int slash = indexOfSlash (nameAndArgumentTypes);
|
|
|
|
if (slash > 0)
|
|
{
|
|
returnType = static_cast<Type> (nameAndArgumentTypes[slash + 1]);
|
|
|
|
while (nameAndArgumentTypes[slash + 2 + numArgs] != 0)
|
|
++numArgs;
|
|
}
|
|
}
|
|
|
|
const char* nameAndArguments; /**< This signature must have the form "name/[return type][arg1][arg2..]" */
|
|
ImplementationFunction function; /**< A static function that will be called. */
|
|
FunctionID functionID; /**< The ID is a hash of the name + arguments, but not the return type. */
|
|
Type returnType; /**< The function's return type. */
|
|
uint8 numArgs; /**< The number of arguments that the function takes. */
|
|
|
|
/** Converts a function signature to its hashed ID. */
|
|
static FunctionID createID (const char* nameAndArgTypes) noexcept
|
|
{
|
|
jassert (nameAndArgTypes != nullptr && nameAndArgTypes[0] != 0); // the name cannot be an empty string!
|
|
int hash = 0, i = 0;
|
|
const int slash = indexOfSlash (nameAndArgTypes);
|
|
|
|
jassert (slash > 0); // The slash can't be the first character in this string!
|
|
jassert (nameAndArgTypes[slash + 1] != 0); // The slash must be followed by a return type character
|
|
jassert (juce::String (nameAndArgTypes).substring (0, slash).containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"));
|
|
jassert (! juce::String ("0123456789").containsChar (nameAndArgTypes[0]));
|
|
jassert (juce::String (nameAndArgTypes).substring (slash + 1).containsOnly ("vifb"));
|
|
jassert (juce::String (nameAndArgTypes).substring (slash + 2).containsOnly ("ifb")); // arguments must only be of these types
|
|
|
|
for (; nameAndArgTypes[i] != 0; ++i)
|
|
if (i != slash + 1)
|
|
hash = hash * 31 + nameAndArgTypes[i];
|
|
|
|
return static_cast<FunctionID> (hash + i);
|
|
}
|
|
|
|
private:
|
|
static int indexOfSlash (const char* nameAndArgs) noexcept
|
|
{
|
|
for (int i = 0; nameAndArgs[i] != 0; ++i)
|
|
if (nameAndArgs[i] == '/')
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
};
|
|
|
|
//==============================================================================
|
|
/**
|
|
A reference to a block of memory which contains a complete program.
|
|
|
|
Data format:
|
|
2 bytes - program checksum
|
|
2 bytes - program size
|
|
2 bytes - num functions
|
|
2 bytes - num globals
|
|
2 bytes - amount of heap space needed (bytes)
|
|
2 bytes - ID of function 1
|
|
2 bytes - byte offset of function 1 code
|
|
2 bytes - ID of function 2
|
|
2 bytes - byte offset of function 2 code
|
|
etc..
|
|
...function code...
|
|
|
|
@tags{Blocks}
|
|
*/
|
|
struct Program
|
|
{
|
|
Program (const void* data, uint32 totalMemorySize) noexcept
|
|
: programStart (static_cast<const uint8*> (data)), maxProgramSize (totalMemorySize)
|
|
{
|
|
jassert (data != nullptr);
|
|
}
|
|
|
|
uint16 getStoredChecksum() const noexcept
|
|
{
|
|
return (uint16) readInt16 (programStart);
|
|
}
|
|
|
|
uint16 calculateChecksum() const noexcept
|
|
{
|
|
auto size = getProgramSize();
|
|
uint16 n = (uint16) size;
|
|
|
|
for (uint32 i = 2; i < size; ++i)
|
|
n += (n * 2) + programStart[i];
|
|
|
|
return n;
|
|
}
|
|
|
|
bool checksumMatches() const noexcept
|
|
{
|
|
return calculateChecksum() == getStoredChecksum();
|
|
}
|
|
|
|
uint32 getNumFunctions() const noexcept
|
|
{
|
|
return (uint16) readInt16 (programStart + 4);
|
|
}
|
|
|
|
FunctionID getFunctionID (uint32 functionIndex) const noexcept
|
|
{
|
|
if (auto f = getFunctionEntry (functionIndex))
|
|
return static_cast<FunctionID> (readInt16 (f));
|
|
|
|
return {};
|
|
}
|
|
|
|
const uint8* getFunctionStartAddress (uint32 functionIndex) const noexcept
|
|
{
|
|
if (auto f = getFunctionEntry (functionIndex))
|
|
{
|
|
uint32 address = (uint16) readInt16 (f + sizeof (FunctionID));
|
|
|
|
if (address < getProgramSize())
|
|
return programStart + address;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
const uint8* getFunctionEndAddress (uint32 functionIndex) const noexcept
|
|
{
|
|
return ++functionIndex >= getNumFunctions() ? programStart + getProgramSize()
|
|
: getFunctionStartAddress (functionIndex);
|
|
}
|
|
|
|
uint32 getProgramSize() const noexcept
|
|
{
|
|
auto size = (uint16) readInt16 (programStart + 2);
|
|
return size < programHeaderSize ? programHeaderSize
|
|
: (size > maxProgramSize ? maxProgramSize : size);
|
|
}
|
|
|
|
/** Returns the number of bytes of heap space the program needs */
|
|
uint16 getHeapSizeBytes() const noexcept
|
|
{
|
|
return (uint16) readInt16 (programStart + 8);
|
|
}
|
|
|
|
/** Returns the number of global variables the program uses */
|
|
uint16 getNumGlobals() const noexcept
|
|
{
|
|
return (uint16) readInt16 (programStart + 6);
|
|
}
|
|
|
|
uint32 getTotalSpaceNeeded() const noexcept
|
|
{
|
|
return getProgramSize() + getHeapSizeBytes();
|
|
}
|
|
|
|
#if JUCE_DEBUG
|
|
//==============================================================================
|
|
/** Prints the assembly code for a given function. */
|
|
void dumpFunctionDisassembly (juce::OutputStream& out, uint32 functionIndex) const
|
|
{
|
|
out << juce::newLine << "Function #" << (int) functionIndex
|
|
<< " (" << juce::String::toHexString (getFunctionID (functionIndex)) << ")" << juce::newLine;
|
|
|
|
if (auto codeStart = getFunctionStartAddress (functionIndex))
|
|
if (auto codeEnd = getFunctionEndAddress (functionIndex))
|
|
for (auto prog = codeStart; prog < codeEnd;)
|
|
out << getOpDisassembly (prog) << juce::newLine;
|
|
}
|
|
|
|
juce::String getOpDisassembly (const uint8*& prog) const
|
|
{
|
|
juce::String s;
|
|
s << juce::String::toHexString ((int) (prog - programStart)).paddedLeft ('0', 4) << ": ";
|
|
auto op = (OpCode) *prog++;
|
|
|
|
switch (op)
|
|
{
|
|
#define LITTLEFOOT_OP(name) case OpCode::name: s << #name; break;
|
|
#define LITTLEFOOT_OP_INT8(name) case OpCode::name: s << #name << " " << juce::String::toHexString ((int) *prog++).paddedLeft ('0', 2); break;
|
|
#define LITTLEFOOT_OP_INT16(name) case OpCode::name: s << #name << " " << juce::String::toHexString ((int) readInt16 (prog)).paddedLeft ('0', 4); prog += 2; break;
|
|
#define LITTLEFOOT_OP_INT32(name) case OpCode::name: s << #name << " " << juce::String::toHexString ((int) readInt32 (prog)).paddedLeft ('0', 8); prog += 4; break;
|
|
LITTLEFOOT_OPCODES (LITTLEFOOT_OP, LITTLEFOOT_OP_INT8, LITTLEFOOT_OP_INT16, LITTLEFOOT_OP_INT32)
|
|
#undef LITTLEFOOT_OP
|
|
#undef LITTLEFOOT_OP_INT8
|
|
#undef LITTLEFOOT_OP_INT16
|
|
#undef LITTLEFOOT_OP_INT32
|
|
|
|
default: s << "???"; break;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/** Calls dumpFunctionDisassembly() for all functions. */
|
|
void dumpAllFunctions (juce::OutputStream& out) const
|
|
{
|
|
if (programStart != nullptr)
|
|
{
|
|
DBG ("Program size: " << (int) getProgramSize() << " bytes");
|
|
|
|
for (uint32 i = 0; i < getNumFunctions(); ++i)
|
|
dumpFunctionDisassembly (out, i);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/** For a given op code, this returns the number of program bytes that follow it. */
|
|
static uint8 getNumExtraBytesForOpcode (OpCode op) noexcept
|
|
{
|
|
switch (op)
|
|
{
|
|
#define LITTLEFOOT_OP(name) case OpCode::name: return 0;
|
|
#define LITTLEFOOT_OP_INT8(name) case OpCode::name: return 1;
|
|
#define LITTLEFOOT_OP_INT16(name) case OpCode::name: return 2;
|
|
#define LITTLEFOOT_OP_INT32(name) case OpCode::name: return 4;
|
|
LITTLEFOOT_OPCODES (LITTLEFOOT_OP, LITTLEFOOT_OP_INT8, LITTLEFOOT_OP_INT16, LITTLEFOOT_OP_INT32)
|
|
#undef LITTLEFOOT_OP
|
|
#undef LITTLEFOOT_OP_INT8
|
|
#undef LITTLEFOOT_OP_INT16
|
|
#undef LITTLEFOOT_OP_INT32
|
|
|
|
default: jassertfalse; return 0;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
static float intToFloat (int32 value) noexcept { float v; copyFloatMem (&v, &value); return v; }
|
|
static int32 floatToInt (float value) noexcept { int32 v; copyFloatMem (&v, &value); return v; }
|
|
|
|
static int16 readInt16 (const uint8* d) noexcept { return (int16) (d[0] | (((uint16) d[1]) << 8)); }
|
|
static int32 readInt32 (const uint8* d) noexcept { return (int32) (d[0] | (((uint32) d[1]) << 8) | (((uint32) d[2]) << 16) | (((uint32) d[3]) << 24)); }
|
|
|
|
static void writeInt16 (uint8* d, int16 v) noexcept { d[0] = (uint8) v; d[1] = (uint8) (v >> 8); }
|
|
static void writeInt32 (uint8* d, int32 v) noexcept { d[0] = (uint8) v; d[1] = (uint8) (v >> 8); d[2] = (uint8) (v >> 16); d[3] = (uint8) (v >> 24); }
|
|
|
|
//==============================================================================
|
|
static constexpr uint32 programHeaderSize = 10;
|
|
const uint8* programStart = 0;
|
|
const uint32 maxProgramSize;
|
|
|
|
private:
|
|
const uint8* getFunctionEntry (uint32 index) const noexcept
|
|
{
|
|
auto offset = programHeaderSize + index * (sizeof (FunctionID) + sizeof (int16));
|
|
return offset <= (uint32) (getProgramSize() - 4) ? programStart + offset : nullptr;
|
|
}
|
|
|
|
static void copyFloatMem (void* dest, const void* src) noexcept
|
|
{
|
|
for (int i = 0; i < 4; ++i)
|
|
((uint8*) dest)[i] = ((const uint8*) src)[i];
|
|
}
|
|
};
|
|
|
|
|
|
//==============================================================================
|
|
/**
|
|
Loads a program, and lets the user execute its functions.
|
|
The programAndHeapSpace is the number of bytes allocated for program + heap.
|
|
stackAndGlobalsSpace is the size of the globals + stack area.
|
|
|
|
Memory layout:
|
|
|
|
Program code goes at address 0, followed by any shared data the program needs
|
|
globals are at the top end of the buffer
|
|
stack space stretches downwards from the start of the globals
|
|
|
|
@tags{Blocks}
|
|
*/
|
|
template <int programAndHeapSpace, int stackAndGlobalsSpace>
|
|
struct Runner
|
|
{
|
|
Runner() noexcept : program (allMemory, sizeof (allMemory)) { reset(); }
|
|
|
|
/** Installs an array of native functions that the code can use.
|
|
Note that this doesn't take ownership of any memory involved, so the caller mustn't pass any dangling pointers
|
|
*/
|
|
void setNativeFunctions (const NativeFunction* functions, int numFunctions, void* userDataForCallback) noexcept
|
|
{
|
|
nativeFunctions = functions;
|
|
numNativeFunctions = numFunctions;
|
|
nativeFunctionCallbackContext = userDataForCallback;
|
|
}
|
|
|
|
/** Returns the number of native functions available. */
|
|
int getNumNativeFunctions() const noexcept { return numNativeFunctions; }
|
|
|
|
/** Returns one of the native functions available. The index must not be out of range. */
|
|
const NativeFunction& getNativeFunction (int index) const noexcept { jassert (index >= 0 && index < numNativeFunctions); return nativeFunctions[index]; }
|
|
|
|
/** Clears the memory state. */
|
|
void reset() noexcept
|
|
{
|
|
for (uint32 i = 0; i < sizeof (allMemory); ++i)
|
|
allMemory[i] = 0;
|
|
}
|
|
|
|
/** Clears all the non-program data. */
|
|
void clearHeapAndGlobals() noexcept
|
|
{
|
|
auto* start = getProgramAndDataStart() + program.getProgramSize();
|
|
auto* end = allMemory + sizeof (allMemory);
|
|
|
|
for (auto m = start; m < end; ++m)
|
|
*m = 0;
|
|
}
|
|
|
|
/** Return codes from a function call */
|
|
enum class ErrorCode : uint8
|
|
{
|
|
ok = 0,
|
|
executionTimedOut,
|
|
unknownInstruction,
|
|
stackOverflow,
|
|
stackUnderflow,
|
|
illegalAddress,
|
|
divisionByZero,
|
|
unknownFunction
|
|
};
|
|
|
|
/** Returns a text description for an error code */
|
|
static const char* getErrorDescription (ErrorCode e) noexcept
|
|
{
|
|
switch (e)
|
|
{
|
|
case ErrorCode::ok: return "OK";
|
|
case ErrorCode::executionTimedOut: return "Timed-out";
|
|
case ErrorCode::unknownInstruction: return "Illegal instruction";
|
|
case ErrorCode::stackOverflow: return "Stack overflow";
|
|
case ErrorCode::stackUnderflow: return "Stack underflow";
|
|
case ErrorCode::illegalAddress: return "Illegal access";
|
|
case ErrorCode::divisionByZero: return "Division by zero";
|
|
case ErrorCode::unknownFunction: return "Unknown function";
|
|
default: return "Unknown error";
|
|
}
|
|
}
|
|
|
|
/** Calls one of the functions in the program, by its textual signature. */
|
|
ErrorCode callFunction (const char* functionSignature) noexcept
|
|
{
|
|
return FunctionExecutionContext (*this, functionSignature).run();
|
|
}
|
|
|
|
/** Calls one of the functions in the program, by its function ID. */
|
|
ErrorCode callFunction (FunctionID function) noexcept
|
|
{
|
|
return FunctionExecutionContext (*this, function).run();
|
|
}
|
|
|
|
/** */
|
|
static constexpr uint32 totalProgramAndHeapSpace = programAndHeapSpace;
|
|
|
|
/** */
|
|
static constexpr uint32 totalStackAndGlobalsSpace = stackAndGlobalsSpace;
|
|
|
|
/** */
|
|
static uint32 getMaximumProgramSize() noexcept { return programAndHeapSpace; }
|
|
|
|
/** */
|
|
uint8* getProgramAndDataStart() const noexcept { return const_cast<uint8*> (allMemory); }
|
|
/** */
|
|
uint8* getProgramAndDataEnd() const noexcept { return reinterpret_cast<uint8*> (stackStart); }
|
|
/** */
|
|
uint32 getProgramAndDataSize() const noexcept { return (uint32) (getProgramAndDataEnd() - getProgramAndDataStart()); }
|
|
|
|
/** */
|
|
uint8* getProgramHeapStart() const noexcept { return heapStart; }
|
|
/** */
|
|
uint8* getProgramHeapEnd() const noexcept { return getProgramAndDataEnd(); }
|
|
/** */
|
|
uint16 getProgramHeapSize() const noexcept { return heapSize; }
|
|
|
|
/** */
|
|
bool isProgramValid() const noexcept { return heapStart != nullptr; }
|
|
|
|
/** Sets a byte of data. */
|
|
void setDataByte (uint32 index, uint8 value) noexcept
|
|
{
|
|
if (index < programAndHeapSpace)
|
|
{
|
|
auto& dest = getProgramAndDataStart()[index];
|
|
|
|
if (index < program.getProgramSize() && dest != value)
|
|
heapStart = nullptr; // force a re-initialise of the memory layout when the program changes
|
|
|
|
dest = value;
|
|
}
|
|
}
|
|
|
|
/** */
|
|
void setHeapByte (uint32 index, uint8 value) noexcept
|
|
{
|
|
auto* addr = getProgramHeapStart() + index;
|
|
|
|
if (addr < getProgramHeapEnd())
|
|
*addr = value;
|
|
}
|
|
|
|
/** */
|
|
uint8 getHeapByte (uint32 index) const noexcept
|
|
{
|
|
const auto* addr = getProgramHeapStart() + index;
|
|
return addr < getProgramHeapEnd() ? *addr : 0;
|
|
}
|
|
|
|
/** */
|
|
uint32 getHeapBits (uint32 startBit, uint32 numBits) const noexcept
|
|
{
|
|
if (startBit + numBits > 8 * getProgramHeapSize())
|
|
{
|
|
jassertfalse;
|
|
return 0;
|
|
}
|
|
|
|
return readLittleEndianBitsInBuffer (getProgramHeapStart(), startBit, numBits);
|
|
}
|
|
|
|
/** */
|
|
int32 setHeapInt (uint32 byteOffset, uint32 value) noexcept
|
|
{
|
|
if (byteOffset + 3 < getProgramHeapSize())
|
|
Program::writeInt32 (getProgramHeapStart() + byteOffset, (int32) value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** */
|
|
int32 getHeapInt (uint32 byteOffset) const noexcept
|
|
{
|
|
return byteOffset + 3 < getProgramHeapSize() ? Program::readInt32 (getProgramHeapStart() + byteOffset) : 0;
|
|
}
|
|
|
|
//==============================================================================
|
|
/** */
|
|
uint8 allMemory[((programAndHeapSpace + stackAndGlobalsSpace) + 3) & ~3];
|
|
|
|
/** */
|
|
Program program;
|
|
|
|
//==============================================================================
|
|
/**
|
|
*/
|
|
struct FunctionExecutionContext
|
|
{
|
|
FunctionExecutionContext() noexcept : programCounter (nullptr) {}
|
|
FunctionExecutionContext (const FunctionExecutionContext&) noexcept = default;
|
|
FunctionExecutionContext& operator= (const FunctionExecutionContext&) noexcept = default;
|
|
|
|
/** */
|
|
FunctionExecutionContext (Runner& r, const char* functionSignature) noexcept
|
|
: FunctionExecutionContext (r, NativeFunction::createID (functionSignature)) {}
|
|
|
|
/** */
|
|
FunctionExecutionContext (Runner& r, FunctionID function) noexcept
|
|
: runner (&r.reinitialiseProgramLayoutIfProgramHasChanged()),
|
|
programBase (r.program.programStart), heapStart (r.heapStart),
|
|
stack (r.stackEnd), stackStart (r.stackStart), stackEnd (r.stackEnd),
|
|
globals (r.globals), heapSize (r.heapSize),
|
|
programSize (r.program.getProgramSize()),
|
|
numGlobals (r.program.getNumGlobals())
|
|
{
|
|
if (r.heapStart != nullptr)
|
|
{
|
|
auto& prog = r.program;
|
|
auto numFunctions = prog.getNumFunctions();
|
|
|
|
for (uint32 i = 0; i < numFunctions; ++i)
|
|
{
|
|
if (prog.getFunctionID (i) == function)
|
|
{
|
|
programCounter = prog.getFunctionStartAddress (i);
|
|
programEnd = r.getProgramHeapStart();
|
|
tos = *--stack = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
programCounter = nullptr;
|
|
}
|
|
|
|
/** */
|
|
bool isValid() const noexcept
|
|
{
|
|
return programCounter != nullptr && runner->heapStart != nullptr;
|
|
}
|
|
|
|
/** */
|
|
void reset() noexcept
|
|
{
|
|
programCounter = nullptr;
|
|
}
|
|
|
|
/** */
|
|
template <typename... Args>
|
|
void setArguments (Args... args) noexcept { pushArguments (args...); push0(); /* (dummy return address) */ }
|
|
|
|
/** */
|
|
template <typename TimeOutCheckFunction>
|
|
ErrorCode run (TimeOutCheckFunction hasTimedOut) noexcept
|
|
{
|
|
if (! isValid())
|
|
return ErrorCode::unknownFunction;
|
|
|
|
error = ErrorCode::unknownInstruction;
|
|
uint16 opsPerformed = 0;
|
|
|
|
for (;;)
|
|
{
|
|
if (programCounter >= programEnd)
|
|
return error;
|
|
|
|
if ((++opsPerformed & 63) == 0 && hasTimedOut())
|
|
return ErrorCode::executionTimedOut;
|
|
|
|
dumpDebugTrace();
|
|
|
|
auto op = (OpCode) *programCounter++;
|
|
|
|
#define LITTLEFOOT_PERFORM_OP(name) case OpCode::name: name(); break;
|
|
#define LITTLEFOOT_PERFORM_OP_INT8(name) case OpCode::name: name ((int8) *programCounter++); break;
|
|
#define LITTLEFOOT_PERFORM_OP_INT16(name) case OpCode::name: name (readProgram16()); break;
|
|
#define LITTLEFOOT_PERFORM_OP_INT32(name) case OpCode::name: name (readProgram32()); break;
|
|
|
|
switch (op)
|
|
{
|
|
LITTLEFOOT_OPCODES (LITTLEFOOT_PERFORM_OP, LITTLEFOOT_PERFORM_OP_INT8, LITTLEFOOT_PERFORM_OP_INT16, LITTLEFOOT_PERFORM_OP_INT32)
|
|
default: setError (ErrorCode::unknownInstruction); break;
|
|
}
|
|
|
|
jassert (programCounter != nullptr);
|
|
}
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
Runner* runner;
|
|
const uint8* programCounter;
|
|
const uint8* programEnd;
|
|
const uint8* programBase;
|
|
uint8* heapStart;
|
|
int32* stack;
|
|
int32* stackStart;
|
|
int32* stackEnd;
|
|
int32* globals;
|
|
uint16 heapSize, programSize, numGlobals;
|
|
int32 tos; // top of stack
|
|
ErrorCode error;
|
|
|
|
template <typename Type1, typename... Args> void pushArguments (Type1 arg1, Args... args) noexcept { pushArguments (args...); pushArguments (arg1); }
|
|
void pushArguments (int32 arg1) noexcept { push32 (arg1); }
|
|
void pushArguments (float arg1) noexcept { push32 (Program::floatToInt (arg1)); }
|
|
|
|
int16 readProgram16() noexcept { auto v = Program::readInt16 (programCounter); programCounter += sizeof (int16); return v; }
|
|
int32 readProgram32() noexcept { auto v = Program::readInt32 (programCounter); programCounter += sizeof (int32); return v; }
|
|
|
|
void setError (ErrorCode e) noexcept { error = e; programCounter = programEnd; jassert (error == ErrorCode::ok); }
|
|
|
|
bool checkStackUnderflow() noexcept { if (stack <= stackEnd) return true; setError (ErrorCode::stackUnderflow); return false; }
|
|
bool flushTopToStack() noexcept { if (--stack < stackStart) { setError (ErrorCode::stackOverflow); return false; } *stack = tos; return true; }
|
|
|
|
using IntBinaryOp = int32 (int32, int32);
|
|
using FloatBinaryOp = float (float, float);
|
|
|
|
void binaryOp (IntBinaryOp f) noexcept { if (checkStackUnderflow()) tos = f (*stack++, tos); }
|
|
void binaryOp (FloatBinaryOp f) noexcept { if (checkStackUnderflow()) tos = Program::floatToInt (f (Program::intToFloat (*stack++), Program::intToFloat (tos))); }
|
|
|
|
void halt() noexcept { setError (ErrorCode::ok); }
|
|
void jump (int16 addr) noexcept { if (((uint16) addr) >= programSize) return setError (ErrorCode::illegalAddress); programCounter = programBase + (uint16) addr; }
|
|
void jumpIfTrue (int16 addr) noexcept { bool v = tos; drop(); if (v) jump (addr); }
|
|
void jumpIfFalse (int16 addr) noexcept { bool v = tos; drop(); if (! v) jump (addr); }
|
|
void call (int16 fnAddr) noexcept { if (flushTopToStack()) { tos = (int32) (programCounter - programBase); jump (fnAddr); } }
|
|
void retVoid (int8 numArgs) noexcept { if (tos == 0) return setError (ErrorCode::ok); auto retAddr = (int16) tos; stack += (uint8) numArgs; if (checkStackUnderflow()) { tos = *stack++; jump (retAddr); } }
|
|
void retValue (int8 numArgs) noexcept { auto retAddr = (int16) *stack++; if (retAddr == 0) return setError (ErrorCode::ok); stack += (uint8) numArgs; if (checkStackUnderflow()) jump (retAddr); }
|
|
void drop() noexcept { if (checkStackUnderflow()) tos = *stack++; }
|
|
void dropMultiple (int8 num) noexcept { if (num < 0) { stack -= num; checkStackUnderflow(); } else { stack += num - 1; drop(); }}
|
|
void pushMultiple0 (int8 num) noexcept { if (stack - num <= stackStart) return setError (ErrorCode::stackOverflow); flushTopToStack(); for (int i = (uint8) num; --i > 0;) *--stack = 0; tos = 0; }
|
|
void push0() noexcept { push32 (0); }
|
|
void push1() noexcept { push32 (1); }
|
|
void push8 (int8 value) noexcept { push32 (value); }
|
|
void push16 (int16 value) noexcept { push32 (value); }
|
|
void push32 (int32 value) noexcept { flushTopToStack(); tos = value; }
|
|
void dup() noexcept { flushTopToStack(); }
|
|
void dupOffset_01() noexcept { dupOffset16 (1); }
|
|
void dupOffset_02() noexcept { dupOffset16 (2); }
|
|
void dupOffset_03() noexcept { dupOffset16 (3); }
|
|
void dupOffset_04() noexcept { dupOffset16 (4); }
|
|
void dupOffset_05() noexcept { dupOffset16 (5); }
|
|
void dupOffset_06() noexcept { dupOffset16 (6); }
|
|
void dupOffset_07() noexcept { dupOffset16 (7); }
|
|
void dupOffset (int8 offset) noexcept { dupOffset16 ((uint8) offset); }
|
|
void dupOffset16 (int16 offset) noexcept { if (flushTopToStack()) { auto addr = stack + offset; if (addr < stackStart || addr >= stackEnd) return setError (ErrorCode::illegalAddress); tos = *addr; } }
|
|
void dropToStack (int8 offset) noexcept { dropToStack16 ((uint8) offset); }
|
|
void dropToStack16 (int16 offset) noexcept { auto addr = stack + offset; if (addr < stackStart || addr >= stackEnd) return setError (ErrorCode::illegalAddress); *addr = tos; drop(); }
|
|
void dupFromGlobal (int16 index) noexcept { if (flushTopToStack()) { if (((uint16) index) >= numGlobals) return setError (ErrorCode::illegalAddress); tos = globals [(uint16) index]; } }
|
|
void dropToGlobal (int16 index) noexcept { if (((uint16) index) >= numGlobals) return setError (ErrorCode::illegalAddress); globals [(uint16) index] = tos; drop(); }
|
|
void int32ToFloat() noexcept { tos = Program::floatToInt (static_cast<float> (tos)); }
|
|
void floatToInt32() noexcept { tos = static_cast<int32> (Program::intToFloat (tos)); }
|
|
void add_int32() noexcept { binaryOp ([] (int32 a, int32 b) { return a + b; }); }
|
|
void add_float() noexcept { binaryOp ([] (float a, float b) { return a + b; }); }
|
|
void mul_int32() noexcept { binaryOp ([] (int32 a, int32 b) { return a * b; }); }
|
|
void mul_float() noexcept { binaryOp ([] (float a, float b) { return a * b; }); }
|
|
void sub_int32() noexcept { binaryOp ([] (int32 a, int32 b) { return a - b; }); }
|
|
void sub_float() noexcept { binaryOp ([] (float a, float b) { return a - b; }); }
|
|
void div_int32() noexcept { if (tos == 0) return setError (ErrorCode::divisionByZero); binaryOp ([] (int32 a, int32 b) { return a / b; }); }
|
|
void div_float() noexcept { if (tos == 0) return setError (ErrorCode::divisionByZero); binaryOp ([] (float a, float b) { return a / b; }); }
|
|
void mod_int32() noexcept { if (tos == 0) return setError (ErrorCode::divisionByZero); binaryOp ([] (int32 a, int32 b) { return a % b; }); }
|
|
void bitwiseOr() noexcept { binaryOp ([] (int32 a, int32 b) { return a | b; }); }
|
|
void bitwiseAnd() noexcept { binaryOp ([] (int32 a, int32 b) { return a & b; }); }
|
|
void bitwiseXor() noexcept { binaryOp ([] (int32 a, int32 b) { return a ^ b; }); }
|
|
void bitShiftLeft() noexcept { binaryOp ([] (int32 a, int32 b) { return a << b; }); }
|
|
void bitShiftRight() noexcept { binaryOp ([] (int32 a, int32 b) { return a >> b; }); }
|
|
void logicalOr() noexcept { binaryOp ([] (int32 a, int32 b) { return (int32) (a || b); }); }
|
|
void logicalAnd() noexcept { binaryOp ([] (int32 a, int32 b) { return (int32) (a && b); }); }
|
|
void logicalNot() noexcept { tos = ! tos; }
|
|
void bitwiseNot() noexcept { tos = ~tos; }
|
|
void testZE_int32() noexcept { tos = (tos == 0); }
|
|
void testNZ_int32() noexcept { tos = (tos != 0); }
|
|
void testGT_int32() noexcept { tos = (tos > 0); }
|
|
void testGE_int32() noexcept { tos = (tos >= 0); }
|
|
void testLT_int32() noexcept { tos = (tos < 0); }
|
|
void testLE_int32() noexcept { tos = (tos <= 0); }
|
|
void testZE_float() noexcept { tos = (Program::intToFloat (tos) == 0.0f); }
|
|
void testNZ_float() noexcept { tos = (Program::intToFloat (tos) != 0.0f); }
|
|
void testGT_float() noexcept { tos = (Program::intToFloat (tos) > 0.0f); }
|
|
void testGE_float() noexcept { tos = (Program::intToFloat (tos) >= 0.0f); }
|
|
void testLT_float() noexcept { tos = (Program::intToFloat (tos) < 0.0f); }
|
|
void testLE_float() noexcept { tos = (Program::intToFloat (tos) <= 0.0f); }
|
|
void getHeapByte() noexcept { tos = runner->getHeapByte ((uint32) tos); }
|
|
void getHeapInt() noexcept { tos = runner->getHeapInt ((uint32) tos); }
|
|
void getHeapBits() noexcept { if (checkStackUnderflow()) tos = runner->getHeapBits ((uint32) tos, (uint32) *stack++); }
|
|
void setHeapByte() noexcept { if (checkStackUnderflow()) runner->setHeapByte ((uint32) tos, (uint8) *stack++); drop(); }
|
|
void setHeapInt() noexcept { if (checkStackUnderflow()) runner->setHeapInt ((uint32) tos, (uint32) *stack++); drop(); }
|
|
|
|
void callNative (FunctionID functionID) noexcept
|
|
{
|
|
auto numFunctions = runner->numNativeFunctions;
|
|
auto* functions = runner->nativeFunctions;
|
|
|
|
for (int i = 0; i < numFunctions; ++i)
|
|
{
|
|
const auto& f = functions[i];
|
|
|
|
if (f.functionID == functionID)
|
|
{
|
|
if (flushTopToStack())
|
|
{
|
|
tos = f.function (runner->nativeFunctionCallbackContext, stack);
|
|
stack += f.numArgs;
|
|
|
|
if (checkStackUnderflow() && f.returnType == Type::void_)
|
|
drop();
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
setError (ErrorCode::unknownFunction);
|
|
}
|
|
|
|
void dumpDebugTrace() const
|
|
{
|
|
#if LITTLEFOOT_DEBUG_TRACE // Dumps the program counter and stack, for debugging
|
|
juce::MemoryOutputStream dump;
|
|
auto progCopy = programCounter;
|
|
dump << juce::String (runner->program.getOpDisassembly (progCopy)).paddedRight (' ', 26)
|
|
<< juce::String::toHexString (tos) << ' ';
|
|
|
|
for (auto s = stack; s < stackEnd; ++s)
|
|
dump << juce::String::toHexString (*s) << ' ';
|
|
|
|
DBG (dump.toString());
|
|
#endif
|
|
}
|
|
};
|
|
|
|
private:
|
|
//==============================================================================
|
|
const NativeFunction* nativeFunctions;
|
|
int numNativeFunctions = 0;
|
|
void* nativeFunctionCallbackContext = nullptr;
|
|
uint8* heapStart = nullptr;
|
|
int32* stackStart = nullptr;
|
|
int32* stackEnd = nullptr;
|
|
int32* globals = nullptr;
|
|
uint16 heapSize = 0;
|
|
|
|
Runner& reinitialiseProgramLayoutIfProgramHasChanged() noexcept
|
|
{
|
|
if (heapStart == nullptr && program.checksumMatches())
|
|
{
|
|
auto numGlobals = program.getNumGlobals();
|
|
globals = reinterpret_cast<int32*> (allMemory + sizeof (allMemory)) - numGlobals;
|
|
heapStart = getProgramAndDataStart() + program.getProgramSize();
|
|
heapSize = program.getHeapSizeBytes();
|
|
stackEnd = globals;
|
|
stackStart = reinterpret_cast<int32*> (heapStart + heapSize);
|
|
|
|
if ((uint8*) globals < heapStart || stackStart + 32 > stackEnd)
|
|
{
|
|
jassertfalse;
|
|
heapStart = nullptr;
|
|
}
|
|
else
|
|
{
|
|
for (uint32 i = 0; i < numGlobals; ++i)
|
|
globals[i] = 0; // clear globals
|
|
|
|
#if LITTLEFOOT_DUMP_PROGRAM
|
|
juce::MemoryOutputStream m;
|
|
program.dumpAllFunctions (m);
|
|
DBG (m.toString());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
};
|
|
}
|