Skip to content

Instantly share code, notes, and snippets.

@vittorioromeo
Last active December 31, 2015 20:29
Show Gist options
  • Save vittorioromeo/8040926 to your computer and use it in GitHub Desktop.
Save vittorioromeo/8040926 to your computer and use it in GitHub Desktop.
Simple virtual machine created for fun (now with WIP function magic)
namespace ssvvm
{
class Value
{
public:
enum class Type{Null, Int, Float};
private:
Type type{Type::Null};
union { int implInt; float implFloat; };
public:
template<typename T> inline static Value create(T mContents) noexcept { return {mContents}; }
inline Value() = default;
template<typename T> inline Value(T mContents) noexcept { setType<T>(); set<T>(mContents); }
template<typename T> inline void setType() noexcept { type = Value::getType<T>(); }
template<typename T> inline void set(T mContents) noexcept;
template<typename T> inline T get() const noexcept;
inline Type getType() const noexcept { return type; }
template<typename T> inline static Type getType() noexcept;
};
template<> inline void Value::set<int>(int mContents) noexcept { assert(type == Type::Int); implInt = mContents; }
template<> inline void Value::set<float>(float mContents) noexcept { assert(type == Type::Float); implFloat = mContents; }
template<> inline int Value::get<int>() const noexcept { assert(type == Type::Int); return implInt; }
template<> inline float Value::get<float>() const noexcept { assert(type == Type::Float); return implFloat; }
template<> inline Value::Type Value::getType<int>() noexcept { return Type::Int; }
template<> inline Value::Type Value::getType<float>() noexcept { return Type::Float; }
struct Register { using Idx = int; Value value; };
namespace Internal
{
template<typename T, std::size_t TIdx, typename TArg> inline void arrayFillHelper(T& mArray, const TArg& mArg) { mArray[TIdx] = Value::create<TArg>(mArg); }
template<typename T, std::size_t TIdx, typename TArg, typename... TArgs> inline void arrayFillHelper(T& mArray, const TArg& mArg, const TArgs&... mArgs)
{
arrayFillHelper<T, TIdx>(mArray, mArg); arrayFillHelper<T, TIdx + 1>(mArray, std::forward<const TArgs&>(mArgs)...);
}
}
class Params
{
public:
static constexpr std::size_t valueCount{3};
private:
std::array<Value, valueCount> values;
public:
inline Params() = default;
template<typename... TArgs> inline Params(const TArgs&... mArgs) noexcept { Internal::arrayFillHelper<decltype(values), 0>(values, mArgs...); }
inline const Value& operator[](std::size_t mIdx) const noexcept { assert(mIdx < valueCount); return values[mIdx]; }
inline const decltype(values)& getValues() const noexcept { return values; } // TODO: remove
};
template<std::size_t TSize> class Registry
{
private:
std::array<Register, TSize> registers;
public:
inline Register& get(std::size_t mIdx) noexcept { assert(mIdx < TSize); return registers[mIdx]; }
inline const Register& get(std::size_t mIdx) const noexcept { assert(mIdx < TSize); return registers[mIdx]; }
inline Value& getValue(std::size_t mIdx) noexcept { return get(mIdx).value; }
inline const Value& getValue(std::size_t mIdx) const noexcept { return get(mIdx).value; }
inline std::size_t getSize() const noexcept { return TSize; }
};
class Stack
{
private:
std::vector<Value> stack;
int baseOffset{0}; // Distance between top and base
public:
inline void pushBaseOffset() noexcept { push(Value::create<int>(baseOffset)); baseOffset = 0; }
inline void popBaseOffset() noexcept { baseOffset = getPop().get<int>(); }
inline void push(Value mValue) noexcept { stack.push_back(mValue); ++baseOffset; }
inline Value getPop() noexcept { assert(!stack.empty()); auto result(stack.back()); stack.pop_back(); --baseOffset; return result; }
inline const Value& getTop() const noexcept { return stack.back(); }
inline Value& getTop() noexcept { return stack.back(); }
inline const Value& getTop(int mOffset) const noexcept { return *(std::end(stack) - mOffset - 1); }
inline Value& getTop(int mOffset) noexcept { return *(std::end(stack) - mOffset - 1); }
inline void pop() noexcept { assert(!stack.empty()); stack.pop_back(); }
inline Value getFromBase(int mOffset) noexcept { return *(std::end(stack) - baseOffset - mOffset - 1); }
inline int getBaseOffset() const { return baseOffset; }
inline const decltype(stack)& getStack() const noexcept { return stack; }
};
enum class OpCode : std::size_t
{
// Virtual machine control
Halt = 0,
// Register instructions
LoadIntCVToR,
LoadFloatCVToR,
MoveRVToR,
// Register-stack instructions
PushRVToS,
PopSVToR,
MoveSBOVToR,
// Stack instructions
PushIntCVToS,
PushFloatCVToS,
PushSVToS,
PopSV,
// Program logic
GoToPI,
GoToPIIfIntRV,
GoToPIIfCompareRVGreater,
GoToPIIfCompareRVSmaller,
GoToPIIfCompareRVEqual,
CallPI,
ReturnPI,
// Register basic arithmetic
IncrementIntRV,
DecrementIntRV,
// Stack basic arithmetic
AddInt2SVs,
AddFloat2SVs,
SubtractInt2SVs,
SubtractFloat2SVs,
MultiplyInt2SVs,
MultiplyFloat2SVs,
DivideInt2SVs,
DivideFloat2SVs,
// Comparisons
CompareIntRVIntRVToR,
CompareIntRVIntSVToR,
CompareIntSVIntSVToR,
CompareIntRVIntCVToR,
CompareIntSVIntCVToR
};
template<typename T> using VMFnPtr = void(T::*)();
template<typename T> inline VMFnPtr<T> getVMFnPtr(OpCode mOpCode) noexcept
{
static VMFnPtr<T> fnPtrs[]
{
// Virtual machine control
&T::halt,
// Register instructions
&T::loadIntCVToR,
&T::loadFloatCVToR,
&T::moveRVToR,
// Register-stack instructions
&T::pushRVToS,
&T::popSVToR,
&T::moveSBOVToR,
// Stack instructions
&T::pushIntCVToS,
&T::pushFloatCVToS,
&T::pushSVToS,
&T::popSV,
// Program logic
&T::goToPI,
&T::goToPIIfIntRV,
&T::goToPIIfCompareRVGreater,
&T::goToPIIfCompareRVSmaller,
&T::goToPIIfCompareRVEqual,
&T::callPI,
&T::returnPI,
// Register basic arithmetic
&T::incrementIntRV,
&T::decrementIntRV,
// Stack basic arithmetic
&T::addInt2SVs,
&T::addFloat2SVs,
&T::subtractInt2SVs,
&T::subtractFloat2SVs,
&T::multiplyInt2SVs,
&T::multiplyFloat2SVs,
&T::divideInt2SVs,
&T::divideFloat2SVs,
// Comparisons
&T::compareIntRVIntRVToR,
&T::compareIntRVIntSVToR,
&T::compareIntSVIntSVToR,
&T::compareIntRVIntCVToR,
&T::compareIntSVIntCVToR
};
return fnPtrs[std::size_t(mOpCode)];
}
struct Instruction
{
using Idx = int;
OpCode opCode;
Params params;
inline Instruction() = default;
template<typename... TArgs> inline Instruction(OpCode mOpCode, TArgs... mArgs) noexcept : opCode{mOpCode}, params{mArgs...} { }
};
struct Program
{
private:
std::vector<Instruction> instructions;
public:
inline Program& operator+=(Instruction mInstruction) { instructions.emplace_back(std::move(mInstruction)); return *this; }
inline const Instruction& operator[](std::size_t mIdx) const noexcept { assert(mIdx < instructions.size()); return instructions[mIdx]; }
inline std::size_t getSize() const noexcept { return instructions.size(); }
};
struct VMOperations
{
inline static Value getIntAddition(const Value& mA, const Value& mB) noexcept { return Value::create<int>(mA.get<int>() + mB.get<int>()); }
inline static Value getFloatAddition(const Value& mA, const Value& mB) noexcept { return Value::create<float>(mA.get<float>() + mB.get<float>()); }
inline static Value getIntSubtraction(const Value& mA, const Value& mB) noexcept { return Value::create<int>(mA.get<int>() - mB.get<int>()); }
inline static Value getFloatSubtraction(const Value& mA, const Value& mB) noexcept { return Value::create<float>(mA.get<float>() - mB.get<float>()); }
inline static Value getIntMultiplication(const Value& mA, const Value& mB) noexcept { return Value::create<int>(mA.get<int>() * mB.get<int>()); }
inline static Value getFloatMultiplication(const Value& mA, const Value& mB) noexcept { return Value::create<float>(mA.get<float>() * mB.get<float>()); }
inline static Value getIntDivision(const Value& mA, const Value& mB) noexcept { return Value::create<int>(mA.get<int>() / mB.get<int>()); }
inline static Value getFloatDivision(const Value& mA, const Value& mB) noexcept { return Value::create<float>(mA.get<float>() / mB.get<float>()); }
inline static Value getIntComparison(const Value& mA, const Value& mB) noexcept
{
if(mA.get<int>() > mB.get<int>()) return {1};
if(mA.get<int>() < mB.get<int>()) return {-1};
return {0};
}
};
namespace Internal
{
template<std::size_t TRegistrySize, bool TDebug> class VMImpl
{
public:
Registry<TRegistrySize> registry;
Stack stack;
Instruction::Idx programCounter{0};
Program program;
Instruction programInstruction;
VMFnPtr<VMImpl> fnPtr;
Params params;
bool running{false};
// Helper functions
inline Value& getRV(const Value& mValueIdx) noexcept { return registry.getValue(mValueIdx.get<Register::Idx>()); }
template<typename T> inline Value execOnStack2(const T& mFn) noexcept
{
Value a{stack.getPop()}, b{stack.getPop()};
if(TDebug)
{
ssvu::lo("execOnStack2") << "Value A\t" << a << "\n";
ssvu::lo("execOnStack2") << "Value B\t" << b << "\n";
}
return mFn(a, b);
}
// Instructions
inline void halt() noexcept
{
running = false;
if(TDebug) ssvu::lo("halt") << "Execution halted" << "\n";
}
inline void loadIntCVToR() noexcept
{
assert(params[1].getType() == Value::Type::Int);
auto& regValue(getRV(params[0]));
regValue = params[1];
if(TDebug)
{
const auto& dbgIdxReg(params[0].get<Register::Idx>());
ssvu::lo("loadIntCVToR") << "Loaded " << params[1] << " into register " << dbgIdxReg << "\n";
}
}
inline void loadFloatCVToR() noexcept
{
assert(params[1].getType() == Value::Type::Float);
auto& regValue(getRV(params[0]));
regValue = params[1];
if(TDebug)
{
const auto& dbgIdxReg(params[0].get<Register::Idx>());
ssvu::lo("loadFloatCVToR") << "Loaded " << params[1] << " into register " << dbgIdxReg << "\n";
}
}
inline void moveRVToR() noexcept
{
const auto& idxDst(params[0].get<Register::Idx>());
const auto& idxSrc(params[1].get<Register::Idx>());
registry.get(idxDst) = registry.get(idxSrc);
if(TDebug)
{
ssvu::lo("moveRVToR") << "Moved register " << idxSrc << " into register " << idxDst << "\n";
ssvu::lo("moveRVToR") << "Both registers' value is now " << registry.get(idxSrc).value << "\n";
}
}
inline void pushRVToS() noexcept
{
const auto& toPush(getRV(params[0]));
stack.push(toPush);
if(TDebug)
{
const auto& dbgIdxReg(params[0].get<Register::Idx>());
ssvu::lo("pushRVToS") << "Pushed register " << dbgIdxReg << " value " << toPush << " onto stack" << "\n";
}
}
inline void popSVToR() noexcept
{
auto& popDst(getRV(params[0]));
popDst = stack.getPop();
if(TDebug)
{
const auto& dbgIdxReg(params[0].get<Register::Idx>());
ssvu::lo("popSVToR") << "Popped value " << popDst << " in register " << dbgIdxReg << " from stack" << "\n";
}
}
inline void moveSBOVToR() noexcept
{
const auto& sbOffset(stack.getFromBase(params[1].get<int>()));
if(TDebug)
{
const auto& dbgIdxReg(params[0].get<Register::Idx>());
ssvu::lo("moveSBOVToR") << "Moved SBO value " << sbOffset << " into register " << dbgIdxReg << " from stack base offset" << "\n";
}
getRV(params[0]) = sbOffset;
}
inline void pushIntCVToS() noexcept
{
if(TDebug)
{
ssvu::lo("pushIntCVToS") << "Pushing constant int value " << params[0] << " on stack" << "\n";
}
assert(params[0].getType() == Value::Type::Int);
stack.push(params[0]);
}
inline void pushFloatCVToS() noexcept
{
if(TDebug)
{
ssvu::lo("pushFloatCVToS") << "Pushing constant float value " << params[0] << " on stack" << "\n";
}
assert(params[0].getType() == Value::Type::Float);
stack.push(params[0]);
}
inline void pushSVToS() noexcept
{
if(TDebug)
{
ssvu::lo("pushSVToS") << "Pushing stack top (duplicating) value " << stack.getTop() << "\n";
}
stack.push(stack.getTop());
}
inline void popSV() noexcept
{
if(TDebug)
{
ssvu::lo("popSV") << "Popping stack top (removing) value " << stack.getTop() << "\n";
}
stack.pop();
}
inline void goToPI() noexcept
{
const auto& jmpDst(params[0].get<Instruction::Idx>());
if(TDebug)
{
ssvu::lo("goToPI") << "Unconditional jump to instruction " << jmpDst << "\n";
}
programCounter = jmpDst;
}
inline void goToPIIfIntRV() noexcept
{
const auto& jmpDst(params[0].get<Instruction::Idx>());
const auto& cndVal(getRV(params[1]));
if(TDebug)
{
const auto& dbgIdxReg(params[1].get<Register::Idx>());
ssvu::lo("goToPIIfIntRV") << "Conditional jump to instruction " << jmpDst << "\n";
ssvu::lo("goToPIIfIntRV") << "Condition: register " << dbgIdxReg << " value " << cndVal << " != 0\n";
}
if(cndVal.template get<int>() != 0)
{
programCounter = jmpDst;
if(TDebug) ssvu::lo("goToPIIfIntRV") << "Conditional jump SUCCESS" << "\n";
}
else if(TDebug) ssvu::lo("goToPIIfIntRV") << "Conditional jump FAILURE" << "\n";
}
inline void goToPIIfCompareRVGreater() noexcept
{
const auto& jmpDst(params[0].get<Instruction::Idx>());
const auto& cndVal(getRV(params[1]));
if(TDebug)
{
const auto& dbgIdxReg(params[1].get<Register::Idx>());
ssvu::lo("goToPIIfCompareRVGreater") << "Conditional jump to instruction " << jmpDst << "\n";
ssvu::lo("goToPIIfCompareRVGreater") << "Condition GREATER: register " << dbgIdxReg << " compare value " << cndVal << " == 1\n";
}
if(cndVal.template get<int>() == 1)
{
programCounter = jmpDst;
if(TDebug) ssvu::lo("goToPIIfCompareRVGreater") << "Conditional jump SUCCESS" << "\n";
}
else if(TDebug) ssvu::lo("goToPIIfCompareRVGreater") << "Conditional jump FAILURE" << "\n";
}
inline void goToPIIfCompareRVSmaller() noexcept
{
const auto& jmpDst(params[0].get<Instruction::Idx>());
const auto& cndVal(getRV(params[1]));
if(TDebug)
{
const auto& dbgIdxReg(params[1].get<Register::Idx>());
ssvu::lo("goToPIIfCompareRVSmaller") << "Conditional jump to instruction " << jmpDst << "\n";
ssvu::lo("goToPIIfCompareRVSmaller") << "Condition SMALLER: register " << dbgIdxReg << " compare value " << cndVal << " == -1\n";
}
if(cndVal.template get<int>() == -1)
{
programCounter = jmpDst;
if(TDebug) ssvu::lo("goToPIIfCompareRVSmaller") << "Conditional jump SUCCESS" << "\n";
}
else if(TDebug) ssvu::lo("goToPIIfCompareRVSmaller") << "Conditional jump FAILURE" << "\n";
}
inline void goToPIIfCompareRVEqual() noexcept
{
const auto& jmpDst(params[0].get<Instruction::Idx>());
const auto& cndVal(getRV(params[1]));
if(TDebug)
{
const auto& dbgIdxReg(params[1].get<Register::Idx>());
ssvu::lo("goToPIIfCompareRVEqual") << "Conditional jump to instruction " << jmpDst << "\n";
ssvu::lo("goToPIIfCompareRVEqual") << "Condition EQUAL: register " << dbgIdxReg << " compare value " << cndVal << " == 0\n";
}
if(cndVal.template get<int>() == 1)
{
programCounter = jmpDst;
if(TDebug) ssvu::lo("goToPIIfCompareRVEqual") << "Conditional jump SUCCESS" << "\n";
}
else if(TDebug) ssvu::lo("goToPIIfCompareRVEqual") << "Conditional jump FAILURE" << "\n";
}
inline void callPI() noexcept
{
const auto& callDst(params[0].get<Instruction::Idx>());
if(TDebug) ssvu::lo("callPI") << "Preparing to call function at instruction " << callDst << "\n";
if(TDebug) ssvu::lo("callPI") << "Push current instruction (" << programCounter << ") on stack (for return)\n";
stack.push(Value::create<Instruction::Idx>(programCounter));
if(TDebug) ssvu::lo("callPI") << "Push current stack base (" << stack.getBaseOffset() << ") and reset it\n";
stack.pushBaseOffset();
if(TDebug) ssvu::lo("callPI") << "Calling function (jumping) at instruction " << callDst << "\n";
programCounter = callDst;
}
inline void returnPI() noexcept
{
if(TDebug)
{
ssvu::lo("returnPI") << "Returning from a function - restoring stack base\n";
ssvu::lo("returnPI") << "\tBefore: " << stack.getBaseOffset() << "\n";
}
stack.popBaseOffset();
const auto& returnDst(stack.getPop().get<Instruction::Idx>());
if(TDebug)
{
ssvu::lo("returnPI") << "\tAfter: " << stack.getBaseOffset() << "\n";
ssvu::lo("callPI") << "Returning (jumping) to instruction at the top of the stack (" << returnDst << ")" << "\n";
}
programCounter = returnDst;
}
inline void incrementIntRV() noexcept
{
auto& regVal(getRV(params[0]));
if(TDebug)
{
const auto& dbgIdxReg(params[0].get<Register::Idx>());
ssvu::lo("incrementIntRV") << "Incrementing value " << regVal << " in register " << dbgIdxReg << "\n";
}
regVal.template set<int>(regVal.template get<int>() + 1);
}
inline void decrementIntRV() noexcept
{
auto& regVal(getRV(params[0]));
if(TDebug)
{
const auto& dbgIdxReg(params[0].get<Register::Idx>());
ssvu::lo("decrementIntRV") << "Decrementing value " << regVal << " in register " << dbgIdxReg << "\n";
}
regVal.template set<int>(regVal.template get<int>() - 1);
}
inline void addInt2SVs() noexcept
{
if(TDebug) ssvu::lo("addInt2SVs") << "Adding 2 ints" << "\n";
stack.push(execOnStack2(VMOperations::getIntAddition));
}
inline void addFloat2SVs() noexcept
{
if(TDebug) ssvu::lo("addFloat2SVs") << "Adding 2 floats" << "\n";
stack.push(execOnStack2(VMOperations::getFloatAddition));
}
inline void subtractInt2SVs() noexcept
{
if(TDebug) ssvu::lo("subtractInt2SVs") << "Subtracting 2 ints" << "\n";
stack.push(execOnStack2(VMOperations::getIntSubtraction));
}
inline void subtractFloat2SVs() noexcept
{
if(TDebug) ssvu::lo("subtractFloat2SVs") << "Subtracting 2 floats" << "\n";
stack.push(execOnStack2(VMOperations::getFloatSubtraction));
}
inline void multiplyInt2SVs() noexcept
{
if(TDebug) ssvu::lo("multiplyInt2SVs") << "Multiplying 2 ints" << "\n";
stack.push(execOnStack2(VMOperations::getIntMultiplication));
}
inline void multiplyFloat2SVs() noexcept
{
if(TDebug) ssvu::lo("multiplyFloat2SVs") << "Multiplying 2 floats" << "\n";
stack.push(execOnStack2(VMOperations::getFloatMultiplication));
}
inline void divideInt2SVs() noexcept
{
if(TDebug) ssvu::lo("divideInt2SVs") << "Dividing 2 ints" << "\n";
stack.push(execOnStack2(VMOperations::getIntDivision));
}
inline void divideFloat2SVs() noexcept
{
if(TDebug) ssvu::lo("divideFloat2SVs") << "Dividing 2 floats" << "\n";
stack.push(execOnStack2(VMOperations::getFloatDivision));
}
inline void compareIntRVIntRVToR() noexcept
{
const auto& idxDst(params[0].get<Register::Idx>());
const auto& idxA(params[1].get<Register::Idx>());
const auto& idxB(params[2].get<Register::Idx>());
const auto& valA(registry.get(idxA).value.template get<int>());
const auto& valB(registry.get(idxB).value.template get<int>());
const auto& result(VMOperations::getIntComparison(valA, valB));
registry.get(idxDst).value = result;
if(TDebug)
{
ssvu::lo("compareIntRVIntRVToR") << "Comparing register value " << valA << " with register value " << valB << " into register " << idxDst << "\n";
ssvu::lo("compareIntRVIntRVToR") << "Register value is now " << result << "\n";
}
}
inline void compareIntRVIntSVToR() noexcept
{
const auto& idxDst(params[0].get<Register::Idx>());
const auto& idxA(params[1].get<Register::Idx>());
const auto& valA(registry.get(idxA).value.template get<int>());
const auto& valB(stack.getTop());
const auto& result(VMOperations::getIntComparison(valA, valB));
registry.get(idxDst).value = result;
if(TDebug)
{
ssvu::lo("compareIntRVIntSVToR") << "Comparing register value " << valA << " with stack value " << valB << " into register " << idxDst << "\n";
ssvu::lo("compareIntRVIntSVToR") << "Register value is now " << result << "\n";
}
}
inline void compareIntSVIntSVToR() noexcept
{
const auto& idxDst(params[0].get<Register::Idx>());
const auto& valA(stack.getTop());
const auto& valB(stack.getTop(1));
const auto& result(VMOperations::getIntComparison(valA, valB));
registry.get(idxDst).value = result;
if(TDebug)
{
ssvu::lo("compareIntRVIntSVToR") << "Comparing stack value " << valA << " with stack value " << valB << " into register " << idxDst << "\n";
ssvu::lo("compareIntRVIntSVToR") << "Register value is now " << result << "\n";
}
}
inline void compareIntRVIntCVToR() noexcept
{
const auto& idxDst(params[0].get<Register::Idx>());
const auto& idxA(params[1].get<Register::Idx>());
const auto& valA(registry.get(idxA).value.template get<int>());
const auto& valB(params[2].get<int>());
const auto& result(VMOperations::getIntComparison(valA, valB));
registry.get(idxDst).value = result;
if(TDebug)
{
ssvu::lo("compareIntRVIntCVToR") << "Comparing register value " << valA << " with constant int " << valB << " into register " << idxDst << "\n";
ssvu::lo("compareIntRVIntCVToR") << "Register value is now " << result << "\n";
}
}
inline void compareIntSVIntCVToR() noexcept
{
const auto& idxDst(params[0].get<Register::Idx>());
const auto& valA(stack.getTop());
const auto& valB(params[1].get<int>());
const auto& result(VMOperations::getIntComparison(valA, valB));
registry.get(idxDst).value = result;
if(TDebug)
{
ssvu::lo("compareIntSVIntCVToR") << "Comparing stack value " << valA << " with constant int " << valB << " into register " << idxDst << "\n";
ssvu::lo("compareIntSVIntCVToR") << "Register value is now " << result << "\n";
}
}
// Execution impl
inline void fetch() noexcept
{
if(TDebug) ssvu::lo("fetch") << "Fetching instruction at " << programCounter << "\n";
programInstruction = program[programCounter++];
}
inline void decode() noexcept
{
if(TDebug) ssvu::lo("decode") << "Decoding instruction: OPCODE(" << int(programInstruction.opCode) << ")" << "\n";
fnPtr = getVMFnPtr<VMImpl>(programInstruction.opCode); params = programInstruction.params;
}
inline void eval() noexcept { (this->*fnPtr)(); }
// Execution interface
inline void run() noexcept
{
running = true;
while(running)
{
fetch(); decode(); eval();
if(TDebug)
{
ssvu::lo() << "\n";
ssvu::lo("run()") << "Printing VM state...\n\n";
const auto& st(stack.getStack());
for(int i{0}; i < int(std::max(st.size(), registry.getSize())); ++i)
{
std::size_t sIdx(st.size() - i - 1);
ssvu::lo() << ((sIdx < st.size()) ? "\t|--------------------|" : "\t ");
if(i < int(registry.getSize())) ssvu::lo() << "\t\tRegister " << i << ": " << registry.get(i).value;
ssvu::lo() << "\n";
if(sIdx < st.size())
{
ssvu::lo() << ((i == int(stack.getBaseOffset())) ? "--->\t" : "\t") << i << "(" << -(int(stack.getBaseOffset()) - i) << ")" << "\t" << st.at(sIdx) << "\n";
}
if(sIdx == 0) ssvu::lo() << "\t|--------------------|\n";
}
ssvu::lo() << std::endl;
}
}
}
inline void setProgram(Program mProgram) noexcept { program = std::move(mProgram); }
template<typename T, typename TTpl, std::size_t TIdx, typename TArg> inline static void makeParamsTuple(TTpl& mTpl, const T& mArray) { std::get<TIdx>(mTpl) = mArray.getValues()[TIdx].template get<TArg>(); }
template<typename T, typename TTpl, std::size_t TIdx, typename TArg1, typename TArg2, typename... TArgs> inline static void makeParamsTuple(TTpl& mTpl, const T& mArray)
{
makeParamsTuple<T, TIdx, TArg1>(mTpl, mArray); makeParamsTuple<T, TIdx + 1, TArg2, TArgs...>(mTpl, mArray);
}
struct AnyFunctionBase
{
inline virtual Value call(const Params& mParams) = 0;
inline virtual ~AnyFunctionBase() { }
};
template<typename TReturn, typename... TArgs> struct AnyFunction : public AnyFunctionBase
{
TReturn(*ptr)(TArgs...);
inline AnyFunction(TReturn(*mPtr)(TArgs...)) : ptr{mPtr} { }
inline Value call(const Params& mParams) override
{
std::tuple<TArgs...> paramsTuple;
makeParamsTuple<Params, std::tuple<TArgs...>, 0, TArgs...>(paramsTuple, mParams);
return ssvu::explode(*ptr, paramsTuple);
}
};
struct BoundFunction
{
Value::Type returnType;
Value::Type paramTypes[Params::valueCount];
std::unique_ptr<AnyFunctionBase> function;
};
template<typename T, std::size_t TIdx, typename TArg> inline static void bfArrayFillHelper(T& mArray) { mArray[TIdx] = Value::getType<TArg>(); }
template<typename T, std::size_t TIdx, typename TArg1, typename TArg2, typename... TArgs> inline static void bfArrayFillHelper(T& mArray)
{
bfArrayFillHelper<T, TIdx, TArg1>(mArray); bfArrayFillHelper<T, TIdx + 1, TArg2, TArgs...>(mArray);
}
template<typename TReturn, typename... TArgs> inline BoundFunction bindCFunction(TReturn(*mCFunctionPtr)(TArgs...))
{
BoundFunction boundFunction;
boundFunction.returnType = Value::getType<TReturn>();
bfArrayFillHelper<decltype(boundFunction.paramTypes), 0, TArgs...>(boundFunction.paramTypes);
boundFunction.function = std::unique_ptr<AnyFunctionBase>(new AnyFunction<TReturn, TArgs...>(mCFunctionPtr));
return boundFunction;
}
};
}
class VirtualMachine : public Internal::VMImpl<6, true> { };
}
namespace ssvu
{
template<> struct Stringifier<ssvvm::Value>
{
template<bool TFmt> inline static void impl(std::ostream& mStream, const ssvvm::Value& mValue)
{
if(mValue.getType() == ssvvm::Value::Type::Int)
{
Internal::printBold<TFmt>(mStream, "INT[");
Internal::callStringifyImpl<TFmt>(mStream, mValue.get<int>());
}
else if(mValue.getType() == ssvvm::Value::Type::Float)
{
Internal::printBold<TFmt>(mStream, "FLOAT[");
Internal::callStringifyImpl<TFmt>(mStream, mValue.get<float>());
}
else Internal::printBold<TFmt>(mStream, "NULL[");
Internal::printBold<TFmt>(mStream, "]");
}
};
}
/*
int main()
{
using IT = ssvvm::OpCode;
ssvvm::VirtualMachine vm;
// 2.f * 15
vm.program += {IT::LoadFloatCVToR, 0, 2.f};
vm.program += {IT::LoadFloatCVToR, 2, 12.f};
vm.program += {IT::LoadIntCVToR, 1, 14};
vm.program += {IT::PushRVToS, 0};
vm.program += {IT::PushRVToS, 2};
vm.program += {IT::MultiplyFloat2SVs};
vm.program += {IT::PopSVToR, 0};
vm.program += {IT::PushRVToS, 0};
vm.program += {IT::PushRVToS, 2};
vm.program += {IT::AddFloat2SVs};
vm.program += {IT::PopSVToR, 0};
vm.program += {IT::DecrementIntRV, 1};
vm.program += {IT::GoToPIIfIntRV, 3, 1};
vm.run();
ssvu::lo() << vm.registry.getValue(0).get<float>() << "\n";
return 0;
}
*/
std::string source{
R"(
//!ssvasm
$require_registers(4);
$define(R0, 0);
$define(R1, 1);
$define(R2, 2);
$define(ROutput, 3);
// _______________________________
// FN_MAIN function
// * entrypoint
// * returns in ROutput
// _______________________________
$label(FN_MAIN);
// Compute the 10th fibonacci number
// Load constants
loadIntCVToR(R0, 10);
// Save registers
pushRVToS(R0);
pushRVToS(R1);
// Push args
pushRVToS(R0);
// Call func
callPI(FN_FIB);
// Get return value
moveRVToR(ROutput, R0);
// Pop args
popSV();
// Restore registers
popSVToR(R1);
popSVToR(R0);
// Push output to stack
pushRVToS(ROutput);
halt();
// _______________________________
// FN_FIB function
// * needs 1 int argument
// * uses R0, R1
// * returns in R0
// _______________________________
$label(FN_FIB);
// Get arg from stack
moveSBOVToR(R0, 2);
// Check if arg is < 2 (put compare result in R1)
compareIntRVIntCVToR(R1, R0, 2);
// Return arg if arg < 2
goToPIIfCompareRVSmaller(FN_FIB_RET_ARG, R1);
// Else return fib(arg - 1) + fib(arg - 2)
// Calculate fib(arg - 1)
// Save registers
pushRVToS(R0);
// Push args
pushIntCVToS(1);
pushRVToS(R0);
subtractInt2SVs();
// Call func
callPI(FN_FIB);
// Get return value
// Return value is in R0, move it to R2
moveRVToR(R2, R0);
// Pop args
popSV();
// Restore registers
popSVToR(R0);
// Push fib(arg - 1) on stack
pushRVToS(R2);
// Calculate fib(arg - 2)
// Save registers
pushRVToS(R0);
// Push args
pushIntCVToS(2);
pushRVToS(R0);
subtractInt2SVs();
// Call func
callPI(FN_FIB);
// Get return value
// Return value is in R0, move it to R2
moveRVToR(R2, R0);
// Pop args
popSV();
// Restore registers
popSVToR(R0);
// Push fib(arg - 2) on stack
pushRVToS(R2);
// Return fib(arg - 1) + fib(arg + 1)
addInt2SVs();
popSVToR(R0);
returnPI();
$label(FN_FIB_RET_ARG);
returnPI();
)"};
ssvvm::Program makeProgram(std::string mSource)
{
struct Tkn
{
std::string str;
bool toDel{false};
Tkn(std::string mStr) : str(std::move(mStr)) { }
};
auto expectTkns = [](std::vector<Tkn>& mTkns, std::size_t mIdxStart, const std::vector<std::string>& mExpect) -> bool
{
if(mIdxStart + mExpect.size() >= mTkns.size()) return false;
std::size_t ei{0u};
for(auto i(mIdxStart); i < mIdxStart + mExpect.size(); ++i)
{
if(mExpect[ei] != "?" && mTkns[i].str != mExpect[ei]) return false;
++ei;
}
return true;
};
auto delTkns = [](std::vector<Tkn>& mTkns, std::size_t mIdxStart, const std::vector<std::string>& mExpect)
{
if(mIdxStart + mExpect.size() >= mTkns.size()) return;
std::size_t ei{0u};
for(auto i(mIdxStart); i < mIdxStart + mExpect.size(); ++i)
{
if(mExpect[ei] == "?" || mTkns[i].str == mExpect[ei]) mTkns[i].toDel = true;
++ei;
}
};
std::vector<std::string> lineByLine;
ssvu::split(lineByLine, mSource, "\n");
// Get rid of comments
for(auto& l : lineByLine)
{
std::size_t commentPos;
while((commentPos = l.find("//")) != std::string::npos) l.erase(commentPos, l.size() - commentPos);
}
mSource = ""; for(auto& s : lineByLine) mSource += s;
// Tokenize
std::vector<std::string> splits{"\n", "\t", " "}, splitsKeep{"(", ")", ";", ","};
std::vector<Tkn> tokens;
for(const auto& x : ssvu::getSplit<ssvu::Split::Normal>(mSource, splits)) tokens.emplace_back(x);
mSource = ""; for(auto& s : tokens) mSource += s.str; tokens.clear();
for(const auto& x : ssvu::getSplit<ssvu::Split::KeepSeparatorAsToken>(mSource, splitsKeep)) tokens.emplace_back(x);
//for(auto& s : tokens) ssvu::lo()<<s.c_str()<<std::endl;
// Preprocess stage 1: $require directives
int requireRegisters{-1};
for(auto i(0u); i < tokens.size(); ++i)
{
if(expectTkns(tokens, i, {"$require_registers", "(", "?", ")", ";"}))
{
requireRegisters = std::atoi(tokens[i + 2].str.c_str());
delTkns(tokens, i, {"$require_registers", "(", "?", ")", ";"});
}
}
ssvu::eraseRemoveIf(tokens, [](const Tkn& mTkn){ return mTkn.toDel; });
// Preprocess stage2: $define directives
std::map<std::string, std::string> defines;
//for(auto& s : tokens) ssvu::lo()<<s.str.c_str()<<std::endl;
for(auto i(0u); i < tokens.size(); ++i)
{
if(expectTkns(tokens, i, {"$define", "(", "?", ",", "?", ")", ";"}))
{
std::string alias{tokens[i + 2].str};
if(defines.count(alias) > 0) throw;
defines[alias] = tokens[i + 4].str;
delTkns(tokens, i, {"$define", "(", "?", ",", "?", ")", ";"});
}
}
ssvu::eraseRemoveIf(tokens, [](const Tkn& mTkn){ return mTkn.toDel; });
for(auto& t : tokens) if(defines.count(t.str) > 0) t.str = defines[t.str];
//for(auto& s : tokens) ssvu::lo()<<s.str.c_str()<<std::endl;
struct Instr
{
std::string idnf{"NULL"};
std::vector<std::string> args;
};
std::vector<Instr> instructions;
Instr currentInstr;
std::size_t tknIdx{0u};
while(tknIdx < tokens.size())
{
currentInstr.idnf = tokens[tknIdx].str;
++tknIdx;
if(tokens[tknIdx].str == "(")
{
if(tokens[tknIdx + 1].str == ")" && tokens[tknIdx + 2].str == ";")
{
instructions.push_back(currentInstr); currentInstr = Instr{};
tknIdx = tknIdx + 3; continue;
}
++tknIdx;
while(tokens[tknIdx].str != ";")
{
currentInstr.args.push_back(ssvu::getReplaced(tokens[tknIdx].str, ".f", ""));
tknIdx += 2;
}
++tknIdx;
instructions.push_back(currentInstr); currentInstr = Instr{};
}
}
//for(auto& s : instructions) ssvu::lo() << s.idnf << " " << s.args << "\n";
std::map<std::string, std::string> labelIdxs;
bool found{true};
while(found)
{
found = false;
for(auto i(0u); i < instructions.size(); ++i)
{
if(instructions[i].idnf == "$label")
{
found = true;
labelIdxs[instructions[i].args[0]] = ssvu::toStr(i);
instructions.erase(std::begin(instructions) + i);
break;
}
}
}
for(auto& kk : labelIdxs) for(auto& i : instructions) for(auto& arg : i.args) if(arg == kk.first) arg = kk.second;
//for(auto& s : instructions) ssvu::lo() << s.idnf << " " << s.args << "\n";
ssvvm::Program result;
int idx{0};
for(auto& s : instructions)
{
ssvu::lo() << idx++ << ":\t" << s.idnf << " " << s.args << "\n";
if(s.idnf == "halt") { if(s.args.size() == 0) result += {ssvvm::OpCode::Halt}; else throw; }
else if(s.idnf == "loadIntCVToR") { if(s.args.size() == 2) result += {ssvvm::OpCode::LoadIntCVToR, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str())}; else throw; }
else if(s.idnf == "loadFloatCVToR") { if(s.args.size() == 2) result += {ssvvm::OpCode::LoadFloatCVToR, std::atoi(s.args[0].c_str()), (float)std::atof(s.args[1].c_str())}; else throw; }
else if(s.idnf == "moveRVToR") { if(s.args.size() == 2) result += {ssvvm::OpCode::MoveRVToR, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str())}; else throw; }
else if(s.idnf == "pushRVToS") { if(s.args.size() == 1) result += {ssvvm::OpCode::PushRVToS, std::atoi(s.args[0].c_str())}; else throw; }
else if(s.idnf == "popSVToR") { if(s.args.size() == 1) result += {ssvvm::OpCode::PopSVToR, std::atoi(s.args[0].c_str())}; else throw; }
else if(s.idnf == "moveSBOVToR") { if(s.args.size() == 2) result += {ssvvm::OpCode::MoveSBOVToR, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str())}; else throw; }
else if(s.idnf == "pushIntCVToS") { if(s.args.size() == 1) result += {ssvvm::OpCode::PushIntCVToS, std::atoi(s.args[0].c_str())}; else throw; }
else if(s.idnf == "pushFloatCVToS") { if(s.args.size() == 1) result += {ssvvm::OpCode::PushFloatCVToS, (float)std::atof(s.args[0].c_str())}; else throw; }
else if(s.idnf == "pushSVToS") { if(s.args.size() == 0) result += {ssvvm::OpCode::PushSVToS}; else throw; }
else if(s.idnf == "popSV") { if(s.args.size() == 0) result += {ssvvm::OpCode::PopSV}; else throw; }
else if(s.idnf == "callPI") { if(s.args.size() == 1) result += {ssvvm::OpCode::CallPI, std::atoi(s.args[0].c_str())}; else throw; }
else if(s.idnf == "returnPI") { if(s.args.size() == 0) result += {ssvvm::OpCode::ReturnPI}; else throw; }
else if(s.idnf == "goToPI") { if(s.args.size() == 1) result += {ssvvm::OpCode::GoToPI, std::atoi(s.args[0].c_str())}; else throw; }
else if(s.idnf == "goToPIIfIntRV") { if(s.args.size() == 2) result += {ssvvm::OpCode::GoToPIIfIntRV, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str())}; else throw; }
else if(s.idnf == "goToPIIfCompareRVGreater") { if(s.args.size() == 2) result += {ssvvm::OpCode::GoToPIIfCompareRVGreater, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str())}; else throw; }
else if(s.idnf == "goToPIIfCompareRVSmaller") { if(s.args.size() == 2) result += {ssvvm::OpCode::GoToPIIfCompareRVSmaller, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str())}; else throw; }
else if(s.idnf == "goToPIIfCompareRVEqual") { if(s.args.size() == 2) result += {ssvvm::OpCode::GoToPIIfCompareRVEqual, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str())}; else throw; }
else if(s.idnf == "incrementIntRV") { if(s.args.size() == 1) result += {ssvvm::OpCode::IncrementIntRV, std::atoi(s.args[0].c_str())}; else throw; }
else if(s.idnf == "decrementIntRV") { if(s.args.size() == 1) result += {ssvvm::OpCode::DecrementIntRV, std::atoi(s.args[0].c_str())}; else throw; }
else if(s.idnf == "addInt2SVs") { if(s.args.size() == 0) result += {ssvvm::OpCode::AddInt2SVs}; else throw; }
else if(s.idnf == "addFloat2SVs") { if(s.args.size() == 0) result += {ssvvm::OpCode::AddFloat2SVs}; else throw; }
else if(s.idnf == "subtractInt2SVs") { if(s.args.size() == 0) result += {ssvvm::OpCode::SubtractInt2SVs}; else throw; }
else if(s.idnf == "subtractFloat2SVs") { if(s.args.size() == 0) result += {ssvvm::OpCode::SubtractFloat2SVs}; else throw; }
else if(s.idnf == "multiplyInt2SVs") { if(s.args.size() == 0) result += {ssvvm::OpCode::MultiplyInt2SVs}; else throw; }
else if(s.idnf == "multiplyFloat2SVs") { if(s.args.size() == 0) result += {ssvvm::OpCode::MultiplyFloat2SVs}; else throw; }
else if(s.idnf == "divideInt2SVs") { if(s.args.size() == 0) result += {ssvvm::OpCode::DivideInt2SVs}; else throw; }
else if(s.idnf == "divideFloat2SVs") { if(s.args.size() == 0) result += {ssvvm::OpCode::DivideFloat2SVs}; else throw; }
else if(s.idnf == "compareIntRVIntRVToR") { if(s.args.size() == 3) result += {ssvvm::OpCode::CompareIntRVIntRVToR, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str()), std::atoi(s.args[2].c_str())}; else throw; }
else if(s.idnf == "compareIntRVIntSVToR") { if(s.args.size() == 2) result += {ssvvm::OpCode::CompareIntRVIntSVToR, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str())}; else throw; }
else if(s.idnf == "compareIntSVIntSVToR") { if(s.args.size() == 1) result += {ssvvm::OpCode::CompareIntSVIntSVToR, std::atoi(s.args[0].c_str())}; else throw; }
else if(s.idnf == "compareIntRVIntCVToR") { if(s.args.size() == 3) result += {ssvvm::OpCode::CompareIntRVIntCVToR, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str()), std::atoi(s.args[2].c_str())}; else throw; }
else if(s.idnf == "compareIntSVIntCVToR") { if(s.args.size() == 2) result += {ssvvm::OpCode::CompareIntSVIntCVToR, std::atoi(s.args[0].c_str()), std::atoi(s.args[1].c_str())}; else throw; }
else throw;
}
return result;
}
int cfunc(int x) { return x * 2; }
int main()
{
SSVU_TEST_RUN_ALL();
ssvvm::Program program{makeProgram(source)};
ssvvm::VirtualMachine vm;
ssvvm::VirtualMachine::BoundFunction bf = vm.bindCFunction(&cfunc);
ssvvm::Params callParams{21};
ssvvm::Value returnValue = bf.function->call(callParams);
ssvu::lo() << returnValue << std::endl;
return 0;
vm.setProgram(program);
vm.run();
//ssvu::lo() << vm.registry.getValue(0).get<float>() << "\n";
//ssvu::lo() << vm.registry.getValue(1).get<float>() << "\n";
return 0;
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment