diff --git a/.clang-tidy b/.clang-tidy index 70951d1..4000d67 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -56,11 +56,11 @@ CheckOptions: - key: readability-identifier-naming.ClassCase value: CamelCase - key: readability-identifier-naming.ClassPrefix - value: 'Fx' + value: 'Fox' - key: readability-identifier-naming.StructCase value: CamelCase - key: readability-identifier-naming.StructPrefix - value: 'Fx' + value: 'Fox' - key: readability-identifier-naming.FunctionCase value: CamelCase - key: readability-identifier-naming.MethodCase diff --git a/FxMPPagedArray.hpp b/FoxMPPagedArray.hpp similarity index 87% rename from FxMPPagedArray.hpp rename to FoxMPPagedArray.hpp index 7b1d6cd..324bcb0 100644 --- a/FxMPPagedArray.hpp +++ b/FoxMPPagedArray.hpp @@ -1,12 +1,12 @@ #pragma once -#include "FxScriptUtil.hpp" +#include "FoxScriptUtil.hpp" -#include #include +#include template -class FxMPPagedArray +class FoxMPPagedArray { public: struct Page @@ -28,14 +28,12 @@ class FxMPPagedArray // { // } - Iterator(Page* current_page, uint32 index) - : mCurrentPage(current_page), mCurrentIndex(index) + Iterator(Page* current_page, uint32 index) : mCurrentPage(current_page), mCurrentIndex(index) { } - Iterator& operator ++ () + Iterator& operator++() { - ++mCurrentIndex; if (mCurrentIndex > mCurrentPage->Size) { @@ -46,7 +44,7 @@ class FxMPPagedArray return *this; } - Iterator operator ++ (int) + Iterator operator++(int) { Iterator iterator = *this; ++*this; @@ -54,9 +52,8 @@ class FxMPPagedArray return iterator; } - Iterator& operator -- () + Iterator& operator--() { - if (mCurrentIndex == 0) { mCurrentPage = mCurrentPage->Prev; mCurrentIndex = mCurrentPage->Size; @@ -67,12 +64,12 @@ class FxMPPagedArray return *this; } - ElementType& operator * () + ElementType& operator*() { return mCurrentPage->Data[mCurrentIndex]; } - bool operator != (const Iterator& other) + bool operator!=(const Iterator& other) { return mCurrentPage && (mCurrentPage != other.mCurrentPage || mCurrentIndex != other.mCurrentIndex); } @@ -81,14 +78,14 @@ class FxMPPagedArray uint32 mCurrentIndex = 0; }; - FxMPPagedArray() = default; + FoxMPPagedArray() = default; - FxMPPagedArray(uint32 page_node_capacity) + FoxMPPagedArray(uint32 page_node_capacity) { Create(page_node_capacity); } - FxMPPagedArray& operator = (const FxMPPagedArray& other) + FoxMPPagedArray& operator=(const FoxMPPagedArray& other) { FirstPage = other.FirstPage; CurrentPage = other.CurrentPage; @@ -101,7 +98,7 @@ class FxMPPagedArray return *this; } - FxMPPagedArray& operator = (FxMPPagedArray&& other) + FoxMPPagedArray& operator=(FoxMPPagedArray&& other) { FirstPage = other.FirstPage; CurrentPage = other.CurrentPage; @@ -149,7 +146,7 @@ class FxMPPagedArray inline size_t Size() const { - //return GetCalculatedSize(); + // return GetCalculatedSize(); return TrackedSize; } @@ -318,7 +315,7 @@ class FxMPPagedArray } - ElementType& operator [] (size_t index) + ElementType& operator[](size_t index) { return Get(index); } @@ -381,7 +378,7 @@ class FxMPPagedArray CurrentPage = nullptr; } - ~FxMPPagedArray() + ~FoxMPPagedArray() { if (FirstPage == nullptr) { return; @@ -396,7 +393,7 @@ class FxMPPagedArray // Allocate and initialize the page object void* allocated_page = std::malloc(sizeof(Page)); if (allocated_page == nullptr) { - FxPanic("FxPagedArray", "Memory error allocating page", 0); + FoxPanic("FoxPagedArray", "Memory error allocating page", 0); return nullptr; // for msvc } @@ -410,7 +407,7 @@ class FxMPPagedArray void* allocated_nodes = FX_SCRIPT_ALLOC_MEMORY(ElementType, (sizeof(ElementType) * PageNodeCapacity)); if (allocated_nodes == nullptr) { - FxPanic("FxPagedArray", "Memory error allocating page data", 0); + FoxPanic("FoxPagedArray", "Memory error allocating page data", 0); return nullptr; // for msvc } @@ -424,7 +421,7 @@ class FxMPPagedArray inline void SizeCheck(uint32 size) const { if (size > PageNodeCapacity) { - FxPanic("FxPagedArray", "The current size of a page is greater than the allocated page node capacity!", 0); + FoxPanic("FoxPagedArray", "The current size of a page is greater than the allocated page node capacity!", 0); } } diff --git a/FoxScript.cpp b/FoxScript.cpp new file mode 100644 index 0000000..cf84b69 --- /dev/null +++ b/FoxScript.cpp @@ -0,0 +1,5078 @@ +#include "FoxScript.hpp" + +#include "FoxScriptUtil.hpp" + +#include + +#include + +#define FX_SCRIPT_SCOPE_GLOBAL_VARS_START_SIZE 32 +#define FX_SCRIPT_SCOPE_LOCAL_VARS_START_SIZE 16 + +#define FX_SCRIPT_SCOPE_GLOBAL_ACTIONS_START_SIZE 32 +#define FX_SCRIPT_SCOPE_LOCAL_ACTIONS_START_SIZE 32 + +using Token = FoxTokenizer::Token; +using TT = FoxTokenizer::TokenType; + +FoxValue FoxValue::None {}; + +void FoxConfigScript::LoadFile(const char* path) +{ + FILE* fp = FoxUtil::FileOpen(path, "rb"); + if (fp == nullptr) { + printf("[ERROR] Could not open config file at '%s'\n", path); + return; + } + + std::fseek(fp, 0, SEEK_END); + size_t file_size = std::ftell(fp); + std::rewind(fp); + + mFileData = FX_SCRIPT_ALLOC_MEMORY(char, file_size); + + size_t read_size = std::fread(mFileData, 1, file_size, fp); + if (read_size != file_size) { + printf("[WARNING] Error reading all data from config file at '%s' (read=%zu, size=%zu)\n", path, read_size, file_size); + } + + FoxTokenizer tokenizer(mFileData, read_size); + tokenizer.Tokenize(); + + + fclose(fp); + + mTokens = std::move(tokenizer.GetTokens()); + + /*for (const auto& token : mTokens) { + token.Print(); + }*/ + + mScopes.Create(8); + mCurrentScope = mScopes.Insert(); + mCurrentScope->Vars.Create(FX_SCRIPT_SCOPE_GLOBAL_VARS_START_SIZE); + mCurrentScope->Functions.Create(FX_SCRIPT_SCOPE_GLOBAL_ACTIONS_START_SIZE); + + CreateInternalVariableTokens(); +} + +Token& FoxConfigScript::GetToken(int offset) +{ + const uint32 idx = mTokenIndex + offset; + if (idx < 0 || idx >= mTokens.Size()) { + printf("SOMETHING IS MISSING\n"); + } + assert(idx >= 0 && idx <= mTokens.Size()); + return mTokens[idx]; +} + +Token& FoxConfigScript::EatToken(TT token_type) +{ + Token& token = GetToken(); + if (token.Type != token_type) { + printf("[ERROR] %u:%u: Unexpected token type %s when expecting %s!\n", token.FileLine, token.FileColumn, + FoxTokenizer::GetTypeName(token.Type), FoxTokenizer::GetTypeName(token_type)); + mHasErrors = true; + } + ++mTokenIndex; + return token; +} + +Token* FoxConfigScript::CreateTokenFromString(FoxTokenizer::TokenType type, const char* text) +{ + const uint32 name_len = strlen(text); + + // Create (fabricate..) the name token + Token* token = FX_SCRIPT_ALLOC_NODE(Token); + token->Start = FX_SCRIPT_ALLOC_MEMORY(char, name_len); + token->End = token->Start + name_len; + token->Length = name_len; + token->Type = type; + + // Copy the name to the buffer in the name token + memcpy(token->Start, text, name_len); + + return token; +} + +void FoxConfigScript::CreateInternalVariableTokens() +{ + mTokenReturnVar = CreateTokenFromString(TT::Identifier, FX_SCRIPT_VAR_RETURN_VAL); +} + +void PrintDocCommentExample(FoxTokenizer::Token* comment, int example_tag_length, bool is_command_mode) +{ + char* start = comment->Start + example_tag_length; + + for (int i = 0; i < comment->Length - example_tag_length; i++) { + char ch = start[i]; + + if (!is_command_mode) { + putchar(ch); + continue; + } + + // If we are in command mode, print the example using the command syntax + if (ch == '(' || ch == ')' || ch == ',') { + putchar(' '); + continue; + } + + if (ch == ' ' || ch == '\t' || ch == ';') { + continue; + } + + putchar(ch); + } + + puts(""); +} + +void PrintDocComment(FoxTokenizer::Token* comment, bool is_command_mode) +{ + const char* example_tag = "EX: "; + const int example_tag_length = 4; + + char* start = comment->Start; + + if (comment->Length > example_tag_length && !strncmp(start, example_tag, example_tag_length)) { + printf("\n\t-> Script : "); + PrintDocCommentExample(comment, example_tag_length, false); + + printf("\t-> Command: "); + PrintDocCommentExample(comment, example_tag_length, true); + + return; + } + + printf("%.*s\n", comment->Length, start); +} + +FoxAstNode* FoxConfigScript::TryParseKeyword(FoxAstBlock* parent_block) +{ + if (mTokenIndex >= mTokens.Size()) { + return nullptr; + } + + Token& tk = GetToken(); + FoxHash hash = tk.GetHash(); + + // function [name] ( < [arg type] [arg name] ...> ) { } + constexpr FoxHash kw_function = FoxHashStr("fn"); + + // local [type] [name] ; + constexpr FoxHash kw_local = FoxHashStr("local"); + + // global [type] [name] ; + constexpr FoxHash kw_global = FoxHashStr("global"); + + // return ; + constexpr FoxHash kw_return = FoxHashStr("return"); + + // help [name of function] ; + constexpr FoxHash kw_help = FoxHashStr("help"); + + if (hash == kw_function) { + EatToken(TT::Identifier); + // ParseFunctionDeclare(); + return ParseFunctionDeclare(); + } + if (hash == kw_local) { + EatToken(TT::Identifier); + return ParseVarDeclare(); + } + if (hash == kw_global) { + EatToken(TT::Identifier); + return ParseVarDeclare(&mScopes[0]); + } + if (hash == kw_return) { + EatToken(TT::Identifier); + + FoxAstNode* return_rhs = nullptr; + + if (GetToken().Type != TT::Semicolon) { + // There is a value that follows, get the value + // FoxAstNode* rhs = ParseRhs(); + return_rhs = ParseRhs(); + + + // Assign the return value to __ReturnVal__ + + // FoxAstVarRef* var_ref = FX_SCRIPT_ALLOC_NODE(FoxAstVarRef); + // var_ref->Name = mTokenReturnVar; + + // FoxAstAssign* assign = FX_SCRIPT_ALLOC_NODE(FoxAstAssign); + // assign->Var = var_ref; + // assign->Rhs = rhs; + + // parent_block->Statements.push_back(assign); + } + + FoxAstReturn* ret = FX_SCRIPT_ALLOC_NODE(FoxAstReturn); + ret->Rhs = return_rhs; + + return ret; + } + if (hash == kw_help) { + EatToken(TT::Identifier); + + FoxTokenizer::Token& func_ref = EatToken(TT::Identifier); + + FoxFunction* function = FindFunction(func_ref.GetHash()); + + if (function) { + for (FoxAstDocComment* comment : function->Declaration->DocComments) { + printf("[DOC] %.*s: ", function->Name->Length, function->Name->Start); + PrintDocComment(comment->Comment, mInCommandMode); + } + } + + return nullptr; + } + + return nullptr; +} + +void FoxConfigScript::PushScope() +{ + FoxScope* current = mCurrentScope; + + FoxScope* new_scope = mScopes.Insert(); + new_scope->Parent = current; + new_scope->Vars.Create(FX_SCRIPT_SCOPE_LOCAL_VARS_START_SIZE); + new_scope->Functions.Create(FX_SCRIPT_SCOPE_LOCAL_ACTIONS_START_SIZE); + + mCurrentScope = new_scope; +} + +void FoxConfigScript::PopScope() +{ + FoxScope* new_scope = mCurrentScope->Parent; + mScopes.RemoveLast(); + + assert(new_scope == &mScopes.GetLast()); + + mCurrentScope = new_scope; +} + +FoxAstVarDecl* FoxConfigScript::InternalVarDeclare(FoxTokenizer::Token* name_token, FoxTokenizer::Token* type_token, FoxScope* scope) +{ + if (scope == nullptr) { + scope = mCurrentScope; + } + + // Allocate the declaration node + FoxAstVarDecl* node = FX_SCRIPT_ALLOC_NODE(FoxAstVarDecl); + + node->Name = name_token; + node->Type = type_token; + node->DefineAsGlobal = (scope == &mScopes[0]); + + // Push the variable to the scope + FoxVar var {name_token, type_token, scope}; + scope->Vars.Insert(var); + + return node; +} + +FoxAstVarDecl* FoxConfigScript::ParseVarDeclare(FoxScope* scope) +{ + if (scope == nullptr) { + scope = mCurrentScope; + } + + Token& type = EatToken(TT::Identifier); + Token& name = EatToken(TT::Identifier); + + FoxAstVarDecl* node = FX_SCRIPT_ALLOC_NODE(FoxAstVarDecl); + + node->Name = &name; + node->Type = &type; + node->DefineAsGlobal = (scope == &mScopes[0]); + + FoxVar var {&type, &name, scope}; + + node->Assignment = TryParseAssignment(node->Name); + /*if (node->Assignment) { + var.Value = node->Assignment->Value; + }*/ + scope->Vars.Insert(var); + + return node; +} + +// FoxVar& FoxConfigScript::ParseVarDeclare() +//{ +// Token& type = EatToken(TT::Identifier); +// Token& name = EatToken(TT::Identifier); +// +// FoxVar var{ name.GetHash(), &type, &name }; +// +// TryParseAssignment(var); +// +// mCurrentScope->Vars.Insert(var); +// +// return mCurrentScope->Vars.GetLast(); +// } + +FoxVar* FoxConfigScript::FindVar(FoxHash hashed_name) +{ + FoxScope* scope = mCurrentScope; + + while (scope) { + FoxVar* var = scope->FindVarInScope(hashed_name); + if (var) { + return var; + } + + scope = scope->Parent; + } + + return nullptr; +} + +FoxExternalFunc* FoxConfigScript::FindExternalFunction(FoxHash hashed_name) +{ + for (FoxExternalFunc& func : mExternalFuncs) { + if (func.HashedName == hashed_name) { + return &func; + } + } + + return nullptr; +} + +FoxFunction* FoxConfigScript::FindFunction(FoxHash hashed_name) +{ + FoxScope* scope = mCurrentScope; + + while (scope) { + FoxFunction* var = scope->FindFunctionInScope(hashed_name); + if (var) { + return var; + } + + scope = scope->Parent; + } + + return nullptr; +} + +void FoxConfigScript::Execute(FoxVM& vm) +{ + DefineDefaultExternalFunctions(); + + mRootBlock = Parse(); + + // If there are errors, exit early + if (mHasErrors || mRootBlock == nullptr) { + return; + } + printf("\n=====\n"); + + + FoxIREmitter ir_emitter; + ir_emitter.BeginEmitting(mRootBlock); + + printf("\n=====\n"); + + FoxIRPrinter ir_printer(ir_emitter.mBytecode); + + ir_printer.Print(); + + printf("\n=====\n"); + + FoxIRToArm64 ir_to_arm64(ir_emitter.mBytecode); + + ir_to_arm64.Print(); + + +#if 0 + FoxBCEmitter emitter; + emitter.BeginEmitting(mRootBlock); + + printf("\n=====\n"); + + FoxBCPrinter printer(emitter.mBytecode); + printer.Print(); + + printf("\n=====\n"); + + FoxTranspilerX86 transpiler(emitter.mBytecode); + transpiler.Print(); + + printf("\n=====\n"); + + for (FoxBytecodeVarHandle& handle : emitter.VarHandles) { + printf("Var(%u) AT %lld\n", handle.HashedName, handle.Offset); + } + + // return; + + vm.mExternalFuncs = mExternalFuncs; + vm.Start(std::move(emitter.mBytecode)); + + for (FoxBytecodeVarHandle& handle : emitter.VarHandles) { + printf("Var(%u) AT %lld -> %u\n", handle.HashedName, handle.Offset, vm.Stack[handle.Offset]); + } + + return; +#endif +} + +#if 0 +bool FoxConfigScript::ExecuteUserCommand(const char* command, FoxInterpreter& interpreter) +{ + mHasErrors = false; + + // If there are errors, exit early + if (mRootBlock == nullptr) { + return false; + } + + uint32 new_token_index = mTokens.Size(); + + uint32 old_token_index = mTokenIndex; + + { + uint32 length_of_command = strlen(command); + + char* data_buffer = FX_SCRIPT_ALLOC_MEMORY(char, length_of_command + 1); + memcpy(data_buffer, command, length_of_command); + + FoxTokenizer tokenizer(data_buffer, length_of_command); + tokenizer.Tokenize(); + + for (FoxTokenizer::Token& token : tokenizer.GetTokens()) { + // token.Print(); + mTokens.Insert(token); + } + + /*for (FoxTokenizer::Token& token : mTokens) { + token.Print(); + }*/ + } + + mTokenIndex = new_token_index; + mInCommandMode = true; + + FoxAstBlock* root_block = FX_SCRIPT_ALLOC_NODE(FoxAstBlock); + + FoxAstNode* statement; + while ((statement = ParseStatementAsCommand(root_block))) { + root_block->Statements.push_back(statement); + } + + // FoxAstNode* node = ParseStatementAsCommand(); + + // Revert the current state if there have been errors + if (root_block == nullptr) { + printf("Ignoring state...\n"); + // Get the size of the tokens + // int current_token_index = mTokens.Size(); + for (int i = new_token_index; i < new_token_index; i++) { + mTokens.RemoveLast(); + } + + mTokenIndex = old_token_index; + + mInCommandMode = false; + + // Since we have reverted state, clear errors flag + mHasErrors = false; + + return false; + } + +#if 0 + + // Run the new block + { + interpreter.mInCommandMode = true; + interpreter.Visit(root_block); + interpreter.mInCommandMode = false; + } +#endif + mInCommandMode = false; + + return true; +} + +#endif + +FoxValue FoxConfigScript::ParseValue() +{ + Token& token = GetToken(); + TT token_type = token.Type; + FoxValue value; + + if (token_type == TT::Identifier) { + FoxVar* var = FindVar(token.GetHash()); + + if (var) { + value.Type = FoxValue::REF; + + FoxAstVarRef* var_ref = FX_SCRIPT_ALLOC_NODE(FoxAstVarRef); + var_ref->Name = var->Name; + var_ref->Scope = var->Scope; + + value.ValueRef = var_ref; + + EatToken(TT::Identifier); + + return value; + } + else { + // We cannot find the definition for the variable, assume that it is an external variable that will be defined + // during the interpret stage. + + printf("ERROR: Undefined reference to variable "); + token.Print(); + printf("\n"); + + // printf("Undefined reference to variable \"%.*s\"! (Hash:%u)\n", token.Length, token.Start, token.GetHash()); + EatToken(TT::Identifier); + } + } + + switch (token_type) { + case TT::Integer: + EatToken(TT::Integer); + value.Type = FoxValue::INT; + value.ValueInt = token.ToInt(); + break; + case TT::Float: + EatToken(TT::Float); + value.Type = FoxValue::FLOAT; + value.ValueFloat = token.ToFloat(); + break; + case TT::String: + EatToken(TT::String); + value.Type = FoxValue::STRING; + value.ValueString = token.GetHeapStr(); + break; + default:; + } + + return value; +} + +#define RETURN_IF_NO_TOKENS(rval_) \ + { \ + if (mTokenIndex >= mTokens.Size()) \ + return (rval_); \ + } + +static bool IsTokenTypeLiteral(FoxTokenizer::TokenType type) +{ + return (type == TT::Integer || type == TT::Float || type == TT::String); +} + +FoxAstNode* FoxConfigScript::ParseRhs() +{ + RETURN_IF_NO_TOKENS(nullptr); + + bool has_parameters = false; + + if (mTokenIndex + 1 < mTokens.Size()) { + TT next_token_type = GetToken(1).Type; + has_parameters = next_token_type == TT::LParen; + + if (mInCommandMode) { + has_parameters = + next_token_type == TT::Identifier || next_token_type == TT::Integer || next_token_type == TT::Float || next_token_type == TT::String; + } + } + + FoxTokenizer::Token& token = GetToken(); + + FoxAstNode* lhs = nullptr; + + if (token.Type == TT::Identifier) { + if (has_parameters) { + lhs = ParseFunctionCall(); + } + else { + FoxExternalFunc* external_function = FindExternalFunction(token.GetHash()); + if (external_function != nullptr) { + lhs = ParseFunctionCall(); + } + + FoxFunction* function = FindFunction(token.GetHash()); + if (function != nullptr) { + lhs = ParseFunctionCall(); + } + } + } + if (!lhs) { + if (IsTokenTypeLiteral(token.Type) || token.Type == TT::Identifier) { + FoxAstLiteral* literal = FX_SCRIPT_ALLOC_NODE(FoxAstLiteral); + + FoxValue value = ParseValue(); + literal->Value = value; + + lhs = literal; + } + else { + lhs = ParseRhs(); + } + } + + + // FoxAstLiteral* literal = FX_SCRIPT_ALLOC_NODE(FoxAstLiteral); + // literal->Value = value; + + TT op_type = GetToken(0).Type; + if (op_type == TT::Plus || op_type == TT::Minus) { + FoxAstBinop* binop = FX_SCRIPT_ALLOC_NODE(FoxAstBinop); + + binop->Left = lhs; + binop->OpToken = &EatToken(op_type); + binop->Right = ParseRhs(); + + return binop; + } + + return lhs; + + /* + RETURN_IF_NO_TOKENS(nullptr); + + bool has_parameters = false; + + if (mTokenIndex + 1 < mTokens.Size()) { + TT next_token_type = GetToken(1).Type; + has_parameters = next_token_type == TT::LParen; + + if (mInCommandMode) { + has_parameters = next_token_type == TT::Identifier || next_token_type == TT::Integer || next_token_type == TT::Float || next_token_type == + TT::String; + } + } + + FoxTokenizer::Token& token = GetToken(); + + FoxAstNode* lhs = nullptr; + + if (token.Type == TT::Identifier) { + if (has_parameters) { + lhs = ParseFunctionCall(); + } + else { + FoxExternalFunc* external_function = FindExternalFunction(token.GetHash()); + if (external_function != nullptr) { + return ParseFunctionCall(); + } + + FoxFunction* function = FindFunction(token.GetHash()); + if (function != nullptr) { + return ParseFunctionCall(); + } + + } + } + + else if (IsTokenTypeLiteral(token.Type) || token.Type == TT::Identifier) { + FoxAstLiteral* literal = FX_SCRIPT_ALLOC_NODE(FoxAstLiteral); + literal->Value = ParseValue(); + lhs = literal; + } + + TT op_type = GetToken(0).Type; + if (op_type == TT::Plus || op_type == TT::Minus) { + FoxAstBinop* binop = FX_SCRIPT_ALLOC_NODE(FoxAstBinop); + + binop->Left = lhs; + binop->OpToken = &EatToken(op_type); + binop->Right = ParseRhs(); + + return binop; + } + + return lhs; + */ +} + +FoxAstAssign* FoxConfigScript::TryParseAssignment(FoxTokenizer::Token* var_name) +{ + if (GetToken().Type != TT::Equals) { + return nullptr; + } + + EatToken(TT::Equals); + + FoxAstAssign* node = FX_SCRIPT_ALLOC_NODE(FoxAstAssign); + + FoxAstVarRef* var_ref = FX_SCRIPT_ALLOC_NODE(FoxAstVarRef); + var_ref->Name = var_name; + var_ref->Scope = mCurrentScope; + node->Var = var_ref; + + // node->Value = ParseValue(); + node->Rhs = ParseRhs(); + + return node; +} + +void FoxConfigScript::DefineExternalVar(const char* type, const char* name, const FoxValue& value) +{ + Token* name_token = FX_SCRIPT_ALLOC_MEMORY(Token, sizeof(Token)); + Token* type_token = FX_SCRIPT_ALLOC_MEMORY(Token, sizeof(Token)); + + { + const uint32 type_len = strlen(type); + + char* type_buffer = FX_SCRIPT_ALLOC_MEMORY(char, (type_len + 1)); + std::strcpy(type_buffer, type); + + type_token->Start = type_buffer; + type_token->Type = TT::Identifier; + type_token->Start[type_len] = 0; + type_token->Length = type_len + 1; + } + + { + const uint32 name_len = strlen(name); + + char* name_buffer = FX_SCRIPT_ALLOC_MEMORY(char, (name_len + 1)); + std::strcpy(name_buffer, name); + + name_token->Start = name_buffer; + name_token->Type = TT::Identifier; + name_token->Start[name_len] = 0; + name_token->Length = name_len + 1; + } + + FoxScope* definition_scope = &mScopes[0]; + + FoxVar var(type_token, name_token, definition_scope, true); + var.Value = value; + + definition_scope->Vars.Insert(var); + + // To prevent the variable data from being deleted here. + var.Name = nullptr; + var.Type = nullptr; +} + +FoxAstNode* FoxConfigScript::ParseStatementAsCommand(FoxAstBlock* parent_block) +{ + if (mHasErrors) { + return nullptr; + } + + RETURN_IF_NO_TOKENS(nullptr); + + // Eat any extraneous semicolons + while (GetToken().Type == TT::Semicolon) { + EatToken(TT::Semicolon); + if (mTokenIndex >= mTokens.Size()) { + return nullptr; + } + } + + // Try to parse as a keyword first + FoxAstNode* node = TryParseKeyword(parent_block); + + if (node) { + RETURN_IF_NO_TOKENS(node); + + EatToken(TT::Semicolon); + return node; + } + + // Check identifier + if (mTokenIndex < mTokens.Size() && GetToken().Type == TT::Identifier) { + TT next_token_type = TT::Unknown; + + if (mTokenIndex + 1 < mTokens.Size()) { + next_token_type = GetToken(1).Type; + } + + if (mTokenIndex + 1 < mTokens.Size() && GetToken(1).Type == TT::Equals) { + Token& assign_name = EatToken(TT::Identifier); + node = TryParseAssignment(&assign_name); + } + // If there is what looks to be a function call, try it + else { + FoxExternalFunc* external_function = FindExternalFunction(GetToken().GetHash()); + if (external_function != nullptr) { + node = ParseFunctionCall(); + } + else { + FoxFunction* function = FindFunction(GetToken().GetHash()); + if (function != nullptr) { + node = ParseFunctionCall(); + } + } + } + // If there is just a semicolon after an identifier, parse as an rhs to print out later + if (!node && next_token_type == TT::Semicolon) { + node = ParseRhs(); + } + } + + RETURN_IF_NO_TOKENS(node); + + EatToken(TT::Semicolon); + + return node; +} + +FoxAstNode* FoxConfigScript::ParseStatement(FoxAstBlock* parent_block) +{ + if (mHasErrors) { + return nullptr; + } + + RETURN_IF_NO_TOKENS(nullptr); + + if (GetToken().Type == TT::Dollar) { + EatToken(TT::Dollar); + + mInCommandMode = true; + + FoxAstCommandMode* cmd_mode = FX_SCRIPT_ALLOC_NODE(FoxAstCommandMode); + cmd_mode->Node = ParseStatementAsCommand(parent_block); + + mInCommandMode = false; + + return cmd_mode; + } + + while (GetToken().Type == TT::DocComment) { + FoxAstDocComment* comment = FX_SCRIPT_ALLOC_NODE(FoxAstDocComment); + comment->Comment = &EatToken(TT::DocComment); + CurrentDocComments.push_back(comment); + + if (mTokenIndex >= mTokens.Size()) { + return nullptr; + } + } + + // Eat any extraneous semicolons + while (GetToken().Type == TT::Semicolon) { + EatToken(TT::Semicolon); + + if (mTokenIndex >= mTokens.Size()) { + return nullptr; + } + } + + FoxAstNode* node = TryParseKeyword(parent_block); + + if (!node && (mTokenIndex < mTokens.Size() && GetToken().Type == TT::Identifier)) { + if (mTokenIndex + 1 < mTokens.Size() && GetToken(1).Type == TT::LParen) { + node = ParseFunctionCall(); + } + else if (mTokenIndex + 1 < mTokens.Size() && GetToken(1).Type == TT::Equals) { + Token& assign_name = EatToken(TT::Identifier); + node = TryParseAssignment(&assign_name); + } + else { + GetToken().Print(); + } + } + + + if (!node) { + return nullptr; + } + + // Blocks do not require semicolons + if (node->NodeType == FX_AST_BLOCK || node->NodeType == FX_AST_ACTIONDECL) { + return node; + } + + EatToken(TT::Semicolon); + + return node; +} + +FoxAstBlock* FoxConfigScript::ParseBlock() +{ + bool in_command = mInCommandMode; + mInCommandMode = false; + + FoxAstBlock* block = FX_SCRIPT_ALLOC_NODE(FoxAstBlock); + + EatToken(TT::LBrace); + + while (GetToken().Type != TT::RBrace) { + FoxAstNode* command = ParseStatement(block); + if (command == nullptr) { + break; + } + block->Statements.push_back(command); + } + + EatToken(TT::RBrace); + + mInCommandMode = in_command; + return block; +} + +FoxAstFunctionDecl* FoxConfigScript::ParseFunctionDeclare() +{ + FoxAstFunctionDecl* node = FX_SCRIPT_ALLOC_NODE(FoxAstFunctionDecl); + + if (!CurrentDocComments.empty()) { + node->DocComments = CurrentDocComments; + CurrentDocComments.clear(); + } + + // Name of the function + Token& name = EatToken(TT::Identifier); + + node->Name = &name; + + PushScope(); + EatToken(TT::LParen); + + FoxAstBlock* params = FX_SCRIPT_ALLOC_NODE(FoxAstBlock); + + // Parse the parameter list + while (GetToken().Type != TT::RParen) { + params->Statements.push_back(ParseVarDeclare()); + + if (GetToken().Type == TT::Comma) { + EatToken(TT::Comma); + continue; + } + + break; + } + + EatToken(TT::RParen); + + // Parse the return type + /*if (GetToken().Type != TT::LBrace) { + FoxAstVarDecl* return_decl = ParseVarDeclare(); + node->ReturnVar = return_decl; + }*/ + + // Check to see if there is a return type provided + if (GetToken().Type != TT::LBrace) { + // There is a return type, declare the __ReturnVal__ variable + + // Get the token for the type + Token& type_token = EatToken(TT::Identifier); + + FoxAstVarDecl* return_decl = InternalVarDeclare(mTokenReturnVar, &type_token); + node->ReturnVar = return_decl; + } + + + node->Block = ParseBlock(); + PopScope(); + + node->Params = params; + + FoxFunction function(&name, mCurrentScope, node->Block, node); + mCurrentScope->Functions.Insert(function); + + return node; +} + +// FoxValue FoxConfigScript::TryCallInternalFunc(FoxHash func_name, std::vector& params) +//{ +// FoxValue return_value; +// +// for (const FoxInternalFunc& func : mInternalFuncs) { +// if (func.HashedName == func_name) { +// func.Func(params, &return_value); +// return return_value; +// } +// } +// +// return return_value; +// } + +FoxAstFunctionCall* FoxConfigScript::ParseFunctionCall() +{ + FoxAstFunctionCall* node = FX_SCRIPT_ALLOC_NODE(FoxAstFunctionCall); + + Token& name = EatToken(TT::Identifier); + + node->HashedName = name.GetHash(); + node->Function = FindFunction(node->HashedName); + + TT end_token_type = TT::Semicolon; + + if (!mInCommandMode) { + end_token_type = TT::RParen; + } + + if (!mInCommandMode || GetToken().Type == TT::LParen) { + EatToken(TT::LParen); + } + + while (GetToken().Type != end_token_type) { + FoxAstNode* param = ParseRhs(); + + if (param == nullptr) { + break; + } + + node->Params.push_back(param); + + TT next_tt = GetToken().Type; + + if (GetToken().Type == TT::Comma) { + EatToken(TT::Comma); + continue; + } + + if (mInCommandMode && (next_tt != TT::RParen && next_tt != TT::Semicolon)) { + continue; + } + + + break; + } + + if (!mInCommandMode || GetToken().Type == TT::RParen) { + EatToken(TT::RParen); + } + + return node; +} + + +// void FoxConfigScript::ParseDoCall() +//{ +// Token& call_name = EatToken(TT::Identifier); +// +// EatToken(TT::LParen); +// +// std::vector params; +// +// while (true) { +// params.push_back(ParseValue()); +// +// if (GetToken().Type == TT::Comma) { +// EatToken(TT::Comma); +// continue; +// } +// +// break; +// } +// +// EatToken(TT::RParen); +// +// TryCallInternalFunc(call_name.GetHash(), params); +// +// printf("Calling "); +// call_name.Print(); +// +// for (const auto& param : params) { +// printf("param : "); +// param.Print(); +// } +// } + + +FoxAstBlock* FoxConfigScript::Parse() +{ + FoxAstBlock* root_block = FX_SCRIPT_ALLOC_NODE(FoxAstBlock); + + FoxAstNode* keyword; + while ((keyword = ParseStatement(root_block))) { + root_block->Statements.push_back(keyword); + } + + /*for (const auto& var : mCurrentScope->Vars) { + var.Print(); + }*/ + + if (mHasErrors) { + return nullptr; + } + + FoxAstPrinter printer(root_block); + printer.Print(root_block); + + return root_block; +} + + +void FoxConfigScript::DefineDefaultExternalFunctions() +{ + // log([int | float | string | ref] args...) + RegisterExternalFunc( + FoxHashStr("log"), {}, // Do not check argument types as we handle it here + [](FoxVM* vm, std::vector& args, FoxValue* return_value) + { + printf("[SCRIPT]: "); + + for (int i = args.size() - 1; i >= 0; i--) { + FoxValue& arg = args[i]; + + // const FoxValue& value = interpreter.GetImmediateValue(arg); + const FoxValue& value = arg; + + switch (value.Type) { + case FoxValue::NONETYPE: + printf("[none]"); + break; + case FoxValue::INT: + printf("%d", value.ValueInt); + break; + case FoxValue::FLOAT: + printf("%f", value.ValueFloat); + break; + case FoxValue::STRING: + printf("%s", value.ValueString); + break; + default: + printf("Unknown type\n"); + break; + } + + putchar(' '); + } + + putchar('\n'); + }, + true // Is variadic? + ); +} + + +void FoxConfigScript::RegisterExternalFunc(FoxHash func_name, std::vector param_types, FoxExternalFunc::FuncType callback, + bool is_variadic) +{ + FoxExternalFunc func { + .HashedName = func_name, + .Function = callback, + .ParameterTypes = param_types, + .IsVariadic = is_variadic, + }; + + mExternalFuncs.push_back(func); +} + + +///////////////////////////////////////// +// Script Bytecode Emitter +///////////////////////////////////////// + +#pragma region BytecodeEmitter + +#include "FoxScriptBytecode.hpp" + +void FoxBCEmitter::BeginEmitting(FoxAstNode* node) +{ + mStackSize = 1024; + + /*mStack = new uint8[1024]; + mStackStart = mStack;*/ + + mBytecode.Create(4096); + VarHandles.Create(64); + + Emit(node); + + printf("\n"); + + PrintBytecode(); +} + +#define RETURN_IF_NO_NODE(node_) \ + if ((node_) == nullptr) { \ + return; \ + } + +#define RETURN_VALUE_IF_NO_NODE(node_, value_) \ + if ((node_) == nullptr) { \ + return (value_); \ + } + +void FoxBCEmitter::Emit(FoxAstNode* node) +{ + RETURN_IF_NO_NODE(node); + + if (node->NodeType == FX_AST_BLOCK) { + return EmitBlock(reinterpret_cast(node)); + } + else if (node->NodeType == FX_AST_ACTIONDECL) { + return EmitFunction(reinterpret_cast(node)); + } + else if (node->NodeType == FX_AST_ACTIONCALL) { + return DoFunctionCall(reinterpret_cast(node)); + } + else if (node->NodeType == FX_AST_ASSIGN) { + return EmitAssign(reinterpret_cast(node)); + } + else if (node->NodeType == FX_AST_VARDECL) { + DoVarDeclare(reinterpret_cast(node)); + return; + } + else if (node->NodeType == FX_AST_RETURN) { + constexpr FoxHash return_val_hash = FoxHashStr(FX_SCRIPT_VAR_RETURN_VAL); + + FoxBytecodeVarHandle* return_var = FindVarHandle(return_val_hash); + + if (return_var) { + DoLoad(return_var->Offset, FX_REG_XR); + } + + EmitJumpReturnToCaller(); + + return; + } +} + +FoxBytecodeVarHandle* FoxBCEmitter::FindVarHandle(FoxHash hashed_name) +{ + for (FoxBytecodeVarHandle& handle : VarHandles) { + if (handle.HashedName == hashed_name) { + return &handle; + } + } + return nullptr; +} + +FoxBytecodeFunctionHandle* FoxBCEmitter::FindFunctionHandle(FoxHash hashed_name) +{ + for (FoxBytecodeFunctionHandle& handle : FunctionHandles) { + if (handle.HashedName == hashed_name) { + return &handle; + } + } + return nullptr; +} + + +FoxBCRegister FoxBCEmitter::FindFreeRegister() +{ + uint16 gp_r = 0x01; + + const uint16 num_gp_regs = FX_REG_X3; + + for (int i = 0; i < num_gp_regs; i++) { + if (!(mRegsInUse & gp_r)) { + // We are starting on 0x01, so register index should be N + 1 + const int register_index = i + 1; + + return static_cast(register_index); + } + + gp_r <<= 1; + } + + return FoxBCRegister::FX_REG_NONE; +} + +const char* FoxBCEmitter::GetRegisterName(FoxBCRegister reg) +{ + switch (reg) { + case FX_REG_NONE: + return "NONE"; + case FX_REG_X0: + return "X0"; + case FX_REG_X1: + return "X1"; + case FX_REG_X2: + return "X2"; + case FX_REG_X3: + return "X3"; + case FX_REG_RA: + return "RA"; + case FX_REG_XR: + return "XR"; + case FX_REG_SP: + return "SP"; + default:; + }; + + return "NONE"; +} + +FoxBCRegister FoxBCEmitter::RegFlagToReg(FoxRegisterFlag reg_flag) +{ + switch (reg_flag) { + case FX_REGFLAG_NONE: + return FX_REG_NONE; + case FX_REGFLAG_X0: + return FX_REG_X0; + case FX_REGFLAG_X1: + return FX_REG_X1; + case FX_REGFLAG_X2: + return FX_REG_X2; + case FX_REGFLAG_X3: + return FX_REG_X3; + case FX_REGFLAG_RA: + return FX_REG_RA; + case FX_REGFLAG_XR: + return FX_REG_XR; + } + + return FX_REG_NONE; +} + +FoxRegisterFlag FoxBCEmitter::RegToRegFlag(FoxBCRegister reg) +{ + switch (reg) { + case FX_REG_NONE: + return FX_REGFLAG_NONE; + case FX_REG_X0: + return FX_REGFLAG_X0; + case FX_REG_X1: + return FX_REGFLAG_X1; + case FX_REG_X2: + return FX_REGFLAG_X2; + case FX_REG_X3: + return FX_REGFLAG_X3; + case FX_REG_RA: + return FX_REGFLAG_RA; + case FX_REG_XR: + return FX_REGFLAG_XR; + case FX_REG_SP: + return FX_REGFLAG_NONE; + default:; + } + + return FX_REGFLAG_NONE; +} + + +void FoxBCEmitter::Write16(uint16 value) +{ + mBytecode.Insert(static_cast(value >> 8)); + mBytecode.Insert(static_cast(value)); +} + +void FoxBCEmitter::Write32(uint32 value) +{ + Write16(static_cast(value >> 16)); + Write16(static_cast(value)); +} + +void FoxBCEmitter::WriteOp(uint8 op_base, uint8 op_spec) +{ + mBytecode.Insert(op_base); + mBytecode.Insert(op_spec); +} + +using RhsMode = FoxBCEmitter::RhsMode; + +#define MARK_REGISTER_USED(regn_) \ + { \ + MarkRegisterUsed(regn_); \ + } +#define MARK_REGISTER_FREE(regn_) \ + { \ + MarkRegisterFree(regn_); \ + } + +void FoxBCEmitter::MarkRegisterUsed(FoxBCRegister reg) +{ + FoxRegisterFlag rflag = RegToRegFlag(reg); + mRegsInUse = static_cast(uint16(mRegsInUse) | uint16(rflag)); +} + +void FoxBCEmitter::MarkRegisterFree(FoxBCRegister reg) +{ + FoxRegisterFlag rflag = RegToRegFlag(reg); + + mRegsInUse = static_cast(uint16(mRegsInUse) & (~uint16(rflag))); +} + +void FoxBCEmitter::EmitSave32(int16 offset, uint32 value) +{ + // SAVE32 [i16 offset] [i32] + WriteOp(OpBase_Save, OpSpecSave_Int32); + + Write16(offset); + Write32(value); +} + +void FoxBCEmitter::EmitSaveReg32(int16 offset, FoxBCRegister reg) +{ + // SAVE32r [i16 offset] [%r32] + WriteOp(OpBase_Save, OpSpecSave_Reg32); + + Write16(offset); + Write16(reg); +} + + +void FoxBCEmitter::EmitSaveAbsolute32(uint32 position, uint32 value) +{ + // SAVE32a [i32 offset] [i32] + WriteOp(OpBase_Save, OpSpecSave_AbsoluteInt32); + + Write32(position); + Write32(value); +} + +void FoxBCEmitter::EmitSaveAbsoluteReg32(uint32 position, FoxBCRegister reg) +{ + // SAVE32r [i32 offset] [%r32] + WriteOp(OpBase_Save, OpSpecSave_AbsoluteReg32); + + Write32(position); + Write16(reg); +} + +void FoxBCEmitter::EmitPush32(uint32 value) +{ + // PUSH32 [i32] + WriteOp(OpBase_Push, OpSpecPush_Int32); + Write32(value); + + mStackOffset += 4; +} + +void FoxBCEmitter::EmitPush32r(FoxBCRegister reg) +{ + // PUSH32r [%r32] + WriteOp(OpBase_Push, OpSpecPush_Reg32); + Write16(reg); + + mStackOffset += 4; +} + + +void FoxBCEmitter::EmitPop32(FoxBCRegister output_reg) +{ + // POP32 [%r32] + WriteOp(OpBase_Pop, (OpSpecPop_Int32 << 4) | (output_reg & 0x0F)); + + mStackOffset -= 4; +} + +void FoxBCEmitter::EmitLoad32(int offset, FoxBCRegister output_reg) +{ + // LOAD [i16] [%r32] + WriteOp(OpBase_Load, (OpSpecLoad_Int32 << 4) | (output_reg & 0x0F)); + Write16(static_cast(offset)); +} + +void FoxBCEmitter::EmitLoadAbsolute32(uint32 position, FoxBCRegister output_reg) +{ + // LOADA [i32] [%r32] + WriteOp(OpBase_Load, (OpSpecLoad_AbsoluteInt32 << 4) | (output_reg & 0x0F)); + Write32(position); +} + +void FoxBCEmitter::EmitJumpRelative(uint16 offset) +{ + WriteOp(OpBase_Jump, OpSpecJump_Relative); + Write16(offset); +} + +void FoxBCEmitter::EmitJumpAbsolute(uint32 position) +{ + WriteOp(OpBase_Jump, OpSpecJump_Absolute); + Write32(position); +} + + +void FoxBCEmitter::EmitJumpAbsoluteReg32(FoxBCRegister reg) +{ + WriteOp(OpBase_Jump, OpSpecJump_AbsoluteReg32); + Write16(reg); +} + +void FoxBCEmitter::EmitJumpCallAbsolute(uint32 position) +{ + WriteOp(OpBase_Jump, OpSpecJump_CallAbsolute); + Write32(position); +} + + +void FoxBCEmitter::EmitJumpCallExternal(FoxHash hashed_name) +{ + WriteOp(OpBase_Jump, OpSpecJump_CallExternal); + Write32(hashed_name); +} + +void FoxBCEmitter::EmitJumpReturnToCaller() +{ + WriteOp(OpBase_Jump, OpSpecJump_ReturnToCaller); +} + +void FoxBCEmitter::EmitMoveInt32(FoxBCRegister reg, uint32 value) +{ + WriteOp(OpBase_Move, (OpSpecMove_Int32 << 4) | (reg & 0x0F)); + Write32(value); +} + +void FoxBCEmitter::EmitParamsStart() +{ + WriteOp(OpBase_Data, OpSpecData_ParamsStart); +} + +void FoxBCEmitter::EmitType(FoxValue::ValueType type) +{ + OpSpecType op_type = OpSpecType_Int; + + if (type == FoxValue::STRING) { + op_type = OpSpecType_String; + } + + WriteOp(OpBase_Type, op_type); +} + +uint32 FoxBCEmitter::EmitDataString(char* str, uint16 length) +{ + WriteOp(OpBase_Data, OpSpecData_String); + + uint32 start_index = mBytecode.Size(); + + uint16 final_length = length + 1; + + // If the length is not a factor of 2 (sizeof uint16) then add a byte of padding + if ((final_length & 0x01)) { + ++final_length; + } + + Write16(final_length); + + for (int i = 0; i < final_length; i += 2) { + mBytecode.Insert(str[i]); + + if (i >= length) { + mBytecode.Insert(0); + break; + } + + mBytecode.Insert(str[i + 1]); + } + + return start_index; +} + + +FoxBCRegister FoxBCEmitter::EmitBinop(FoxAstBinop* binop, FoxBytecodeVarHandle* handle) +{ + bool will_preserve_lhs = false; + // Load the A and B values into the registers + FoxBCRegister a_reg = EmitRhs(binop->Left, RhsMode::RHS_FETCH_TO_REGISTER, handle); + + // Since there is a chance that this register will be clobbered (by binop, function call, etc), we will + // push the value of the register here and return it after processing the RHS + if (binop->Right->NodeType != FX_AST_LITERAL) { + will_preserve_lhs = true; + EmitPush32r(a_reg); + } + + FoxBCRegister b_reg = EmitRhs(binop->Right, RhsMode::RHS_FETCH_TO_REGISTER, handle); + + // Retrieve the previous LHS + if (will_preserve_lhs) { + EmitPop32(a_reg); + } + + if (binop->OpToken->Type == TT::Plus) { + WriteOp(OpBase_Arith, OpSpecArith_Add); + + mBytecode.Insert(a_reg); + mBytecode.Insert(b_reg); + } + + // We no longer need the lhs or rhs registers, free em + MARK_REGISTER_FREE(a_reg); + MARK_REGISTER_FREE(b_reg); + + return FX_REG_XR; +} + +FoxBCRegister FoxBCEmitter::EmitVarFetch(FoxAstVarRef* ref, RhsMode mode) +{ + FoxBytecodeVarHandle* var_handle = FindVarHandle(ref->Name->GetHash()); + + bool force_absolute_load = false; + + // If the variable is from a previous scope, load it from an absolute address. local offsets + // will change depending on where they are called. + if (var_handle->ScopeIndex < mScopeIndex) { + force_absolute_load = true; + } + + if (!var_handle) { + printf("Could not find var handle!"); + return FX_REG_NONE; + } + + FoxBCRegister reg = FindFreeRegister(); + + MARK_REGISTER_USED(reg); + + DoLoad(var_handle->Offset, reg, force_absolute_load); + + if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { + return reg; + } + + // If we are just copying the variable to this new variable, we can free the register after + // we push to the stack. + if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { + if (var_handle->Type == FoxValue::STRING) { + EmitType(var_handle->Type); + } + + EmitPush32r(reg); + MARK_REGISTER_FREE(reg); + + return FX_REG_NONE; + } + + // This is a reference to a variable, return the register we loaded it into + return reg; +} + +void FoxBCEmitter::DoLoad(uint32 stack_offset, FoxBCRegister output_reg, bool force_absolute) +{ + if (stack_offset < 0xFFFE && !force_absolute) { + // Relative load + + // Calculate the relative index to the current stack offset + int input_offset = -(static_cast(mStackOffset) - static_cast(stack_offset)); + + EmitLoad32(input_offset, output_reg); + } + else { + // Absolute load + EmitLoadAbsolute32(stack_offset, output_reg); + } +} + +void FoxBCEmitter::DoSaveInt32(uint32 stack_offset, uint32 value, bool force_absolute) +{ + if (stack_offset < 0xFFFE && !force_absolute) { + // Relative save + + // Calculate the relative index to the current stack offset + int input_offset = -(static_cast(mStackOffset) - static_cast(stack_offset)); + + EmitSave32(input_offset, value); + } + else { + // Absolute save + EmitSaveAbsolute32(stack_offset, value); + } +} + +void FoxBCEmitter::DoSaveReg32(uint32 stack_offset, FoxBCRegister reg, bool force_absolute) +{ + if (stack_offset < 0xFFFE && !force_absolute) { + // Relative save + + // Calculate the relative index to the current stack offset + int input_offset = -(static_cast(mStackOffset) - static_cast(stack_offset)); + + EmitSaveReg32(input_offset, reg); + } + else { + // Absolute save + EmitSaveAbsoluteReg32(stack_offset, reg); + } +} + +void FoxBCEmitter::EmitAssign(FoxAstAssign* assign) +{ + FoxBytecodeVarHandle* var_handle = FindVarHandle(assign->Var->Name->GetHash()); + if (var_handle == nullptr) { + printf("!!! Var '%.*s' does not exist!\n", assign->Var->Name->Length, assign->Var->Name->Start); + return; + } + + // bool force_absolute_save = false; + + // if (var_handle->ScopeIndex < mScopeIndex) { + // force_absolute_save = true; + // } + + // int output_offset = -(static_cast(mStackOffset) - static_cast(var_handle->Offset)); + + if (!var_handle) { + printf("Could not find var handle to assign to!"); + return; + } + + EmitRhs(assign->Rhs, RhsMode::RHS_ASSIGN_TO_HANDLE, var_handle); +} + +FoxBCRegister FoxBCEmitter::EmitLiteralInt(FoxAstLiteral* literal, RhsMode mode, FoxBytecodeVarHandle* handle) +{ + // If this is on variable definition, push the value to the stack. + if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { + EmitPush32(literal->Value.ValueInt); + + return FX_REG_NONE; + } + + // If this is as a literal, push the value to the stack and pop onto the target register. + else if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { + // EmitPush32(literal->Value.ValueInt); + + FoxBCRegister output_reg = FindFreeRegister(); + // EmitPop32(output_reg); + + EmitMoveInt32(output_reg, literal->Value.ValueInt); + + // Mark the output register as used to store it + MARK_REGISTER_USED(output_reg); + + return output_reg; + } + + else if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { + const bool force_absolute_save = (handle->ScopeIndex < mScopeIndex); + DoSaveInt32(handle->Offset, literal->Value.ValueInt, force_absolute_save); + + return FX_REG_NONE; + } + + return FX_REG_NONE; +} + + +FoxBCRegister FoxBCEmitter::EmitLiteralString(FoxAstLiteral* literal, RhsMode mode, FoxBytecodeVarHandle* handle) +{ + const uint32 string_length = strlen(literal->Value.ValueString); + + // Emit the length and string data + const uint32 string_position = EmitDataString(literal->Value.ValueString, string_length); + + // local string some_value = "Some String"; + if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { + // Push the location and mark it as a pointer to a string + EmitType(FoxValue::STRING); + EmitPush32(string_position); + + return FX_REG_NONE; + } + + // some_function("Some String") + else if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { + // Push the location for the string and pop it back to a register. + EmitType(FoxValue::STRING); + + // Push the string position + // EmitPush32(string_position); + + // Find a register to output to and write the index + FoxBCRegister output_reg = FindFreeRegister(); + // EmitPop32(output_reg); + + EmitMoveInt32(output_reg, string_position); + + // Mark the output register as used to store it + MARK_REGISTER_USED(output_reg); + + return output_reg; + } + + // some_previous_value = "Some String"; + else if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { + const bool force_absolute_save = (handle->ScopeIndex < mScopeIndex); + + DoSaveInt32(handle->Offset, string_position, force_absolute_save); + handle->Type = FoxValue::STRING; + + return FX_REG_NONE; + } + + return FX_REG_NONE; +} + +FoxBCRegister FoxBCEmitter::EmitRhs(FoxAstNode* rhs, FoxBCEmitter::RhsMode mode, FoxBytecodeVarHandle* handle) +{ + if (rhs->NodeType == FX_AST_LITERAL) { + FoxAstLiteral* literal = reinterpret_cast(rhs); + + if (literal->Value.Type == FoxValue::INT) { + return EmitLiteralInt(literal, mode, handle); + } + else if (literal->Value.Type == FoxValue::STRING) { + return EmitLiteralString(literal, mode, handle); + } + else if (literal->Value.Type == FoxValue::REF) { + // Reference another value, load from memory into register + FoxBCRegister output_register = EmitVarFetch(literal->Value.ValueRef, mode); + if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { + DoSaveReg32(handle->Offset, output_register); + } + + return output_register; + } + + return FX_REG_NONE; + } + + else if (rhs->NodeType == FX_AST_ACTIONCALL || rhs->NodeType == FX_AST_BINOP) { + FoxBCRegister result_register = FX_REG_XR; + + if (rhs->NodeType == FX_AST_BINOP) { + result_register = EmitBinop(reinterpret_cast(rhs), handle); + } + + else if (rhs->NodeType == FX_AST_ACTIONCALL) { + DoFunctionCall(reinterpret_cast(rhs)); + // Function results are stored in XR + result_register = FX_REG_XR; + } + + + if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { + uint32 offset = mStackOffset; + EmitPush32r(result_register); + + if (handle) { + handle->Offset = offset; + } + + return FX_REG_NONE; + } + + else if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { + // Push the result to a register + EmitPush32r(result_register); + + // Find a register to output to, and pop the value to there. + FoxBCRegister output_reg = FindFreeRegister(); + EmitPop32(output_reg); + + // Mark the output register as used to store it + MARK_REGISTER_USED(output_reg); + + return output_reg; + } + else if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { + const bool force_absolute_save = (handle->ScopeIndex < mScopeIndex); + + // Save the value back to the variable + DoSaveReg32(handle->Offset, result_register, force_absolute_save); + + return FX_REG_NONE; + } + } + + return FX_REG_NONE; +} + +FoxBytecodeVarHandle* FoxBCEmitter::DoVarDeclare(FoxAstVarDecl* decl, VarDeclareMode mode) +{ + RETURN_VALUE_IF_NO_NODE(decl, nullptr); + + const uint16 size_of_type = static_cast(sizeof(int32)); + + const FoxHash type_int = FoxHashStr("int"); + const FoxHash type_string = FoxHashStr("string"); + + FoxHash decl_hash = decl->Name->GetHash(); + + FoxValue::ValueType value_type = FoxValue::INT; + + switch (decl_hash) { + case type_int: + value_type = FoxValue::INT; + break; + case type_string: + value_type = FoxValue::STRING; + break; + }; + + FoxBytecodeVarHandle handle { + .HashedName = decl->Name->GetHash(), + .Type = value_type, // Just int for now + .Offset = (mStackOffset), + .SizeOnStack = size_of_type, + .ScopeIndex = mScopeIndex, + }; + + VarHandles.Insert(handle); + + FoxBytecodeVarHandle* inserted_handle = &VarHandles[VarHandles.Size() - 1]; + + if (mode == DECLARE_NO_EMIT) { + // Do not emit any values + return inserted_handle; + } + + + if (decl->Assignment) { + FoxAstNode* rhs = decl->Assignment->Rhs; + + EmitRhs(rhs, RhsMode::RHS_DEFINE_IN_MEMORY, inserted_handle); + + + // EmitPush32(0); + + // EmitRhs(rhs, RhsMode::RHS_ASSIGN_TO_HANDLE, inserted_handle); + } + else { + // There is no assignment, push zero as the value for now and + // a later assignment can set it using save32. + EmitPush32(0); + } + + return inserted_handle; +} + +void FoxBCEmitter::DoFunctionCall(FoxAstFunctionCall* call) +{ + RETURN_IF_NO_NODE(call); + + FoxBytecodeFunctionHandle* handle = FindFunctionHandle(call->HashedName); + + + std::vector call_locations; + call_locations.reserve(8); + + // Push all params to stack + for (FoxAstNode* param : call->Params) { + // FoxBCRegister reg = + if (param->NodeType == FX_AST_ACTIONCALL) { + EmitRhs(param, RhsMode::RHS_DEFINE_IN_MEMORY, nullptr); + call_locations.push_back(mStackOffset - 4); + } + // MARK_REGISTER_FREE(reg); + } + + EmitPush32r(FX_REG_RA); + + EmitParamsStart(); + + int call_location_index = 0; + + // Push all params to stack + for (FoxAstNode* param : call->Params) { + if (param->NodeType == FX_AST_ACTIONCALL) { + FoxBCRegister temp_register = FindFreeRegister(); + + DoLoad(call_locations[call_location_index], temp_register); + call_location_index++; + + EmitPush32r(temp_register); + + continue; + } + + EmitRhs(param, RhsMode::RHS_DEFINE_IN_MEMORY, nullptr); + } + + // The handle could not be found, write it as a possible external symbol. + if (!handle) { + printf("Call name-> %u\n", call->HashedName); + + // Since popping the parameters are handled internally in the VM, + // we need to decrement the stack offset here. + for (int i = 0; i < call->Params.size(); i++) { + mStackOffset -= 4; + } + + EmitJumpCallExternal(call->HashedName); + + EmitPop32(FX_REG_RA); + return; + } + + EmitJumpCallAbsolute(handle->BytecodeIndex); + + EmitPop32(FX_REG_RA); +} + +FoxBytecodeVarHandle* FoxBCEmitter::DefineAndFetchParam(FoxAstNode* param_decl_node) +{ + if (param_decl_node->NodeType != FX_AST_VARDECL) { + printf("!!! param node type is not vardecl!\n"); + return nullptr; + } + + // Emit variable without emitting pushes or pops + FoxBytecodeVarHandle* handle = DoVarDeclare(reinterpret_cast(param_decl_node), DECLARE_NO_EMIT); + + if (!handle) { + printf("!!! could not define and fetch param!\n"); + return nullptr; + } + + assert(handle->SizeOnStack == 4); + + mStackOffset += handle->SizeOnStack; + + return handle; +} + +FoxBytecodeVarHandle* FoxBCEmitter::DefineReturnVar(FoxAstVarDecl* decl) +{ + RETURN_VALUE_IF_NO_NODE(decl, nullptr); + + return DoVarDeclare(decl); +} + +void FoxBCEmitter::EmitFunction(FoxAstFunctionDecl* function) +{ + RETURN_IF_NO_NODE(function); + + ++mScopeIndex; + + // Store the bytecode offset before the function is emitted + + const size_t start_of_function = mBytecode.Size(); + printf("Start of function %zu\n", start_of_function); + + // Emit the jump instruction, we will update the jump position after emitting all of the code inside the block + EmitJumpRelative(0); + + // const uint32 initial_stack_offset = mStackOffset; + + const size_t header_jump_start_index = start_of_function + sizeof(uint16); + + size_t start_var_handle_count = VarHandles.Size(); + + // Offset for the pushed return address + mStackOffset += 4; + + // Emit the body of the function + { + for (FoxAstNode* param_decl_node : function->Params->Statements) { + DefineAndFetchParam(param_decl_node); + } + + FoxBytecodeVarHandle* return_var = DefineReturnVar(function->ReturnVar); + + EmitBlock(function->Block); + + // Check to see if there has been a return statement in the function + bool block_has_return = false; + + for (FoxAstNode* statement : function->Block->Statements) { + if (statement->NodeType == FX_AST_RETURN) { + block_has_return = true; + break; + } + } + + // There is no return statement in the function's block, add a return statement + if (!block_has_return) { + EmitJumpReturnToCaller(); + + if (return_var != nullptr) { + DoLoad(return_var->Offset, FX_REG_XR); + } + } + } + + // Return offset back to pre-call + mStackOffset -= 4; + + const size_t end_of_function = mBytecode.Size(); + const uint16 distance_to_function = static_cast(end_of_function - (start_of_function)-4); + + // Update the jump to the end of the function + mBytecode[header_jump_start_index] = static_cast(distance_to_function >> 8); + mBytecode[header_jump_start_index + 1] = static_cast((distance_to_function & 0xFF)); + + FoxBytecodeFunctionHandle function_handle {.HashedName = function->Name->GetHash(), .BytecodeIndex = static_cast(start_of_function + 4)}; + + const size_t number_of_scope_var_handles = VarHandles.Size() - start_var_handle_count; + printf("Number of var handles to remove: %zu\n", number_of_scope_var_handles); + + FunctionHandles.push_back(function_handle); + + --mScopeIndex; + + // Delete the variables on the stack + for (int i = 0; i < number_of_scope_var_handles; i++) { + FoxBytecodeVarHandle* var = VarHandles.RemoveLast(); + assert(var->SizeOnStack == 4); + mStackOffset -= var->SizeOnStack; + } +} + +void FoxBCEmitter::EmitBlock(FoxAstBlock* block) +{ + RETURN_IF_NO_NODE(block); + + for (FoxAstNode* node : block->Statements) { + Emit(node); + } +} + +void FoxBCEmitter::PrintBytecode() +{ + const size_t size = mBytecode.Size(); + for (int i = 0; i < 25; i++) { + printf("%02d ", i); + } + printf("\n"); + for (int i = 0; i < 25; i++) { + printf("---"); + } + printf("\n"); + + for (size_t i = 0; i < size; i++) { + printf("%02X ", mBytecode[i]); + + if (i > 0 && ((i + 1) % 25) == 0) { + printf("\n"); + } + } + printf("\n"); +} + +#pragma endregion BytecodeEmitter + + +///////////////////////////////////// +// Bytecode Printer +///////////////////////////////////// + +uint16 FoxBCPrinter::Read16() +{ + uint8 lo = mBytecode[mBytecodeIndex++]; + uint8 hi = mBytecode[mBytecodeIndex++]; + + return ((static_cast(lo) << 8) | hi); +} + +uint32 FoxBCPrinter::Read32() +{ + uint16 lo = Read16(); + uint16 hi = Read16(); + + return ((static_cast(lo) << 16) | hi); +} + +#define BC_PRINT_OP(fmt_, ...) snprintf(s, 128, fmt_, ##__VA_ARGS__) + +void FoxBCPrinter::DoLoad(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == OpSpecLoad_Int32) { + int16 offset = Read16(); + BC_PRINT_OP("load32 %d, %s", offset, FoxBCEmitter::GetRegisterName(static_cast(op_reg))); + } + else if (op_spec == OpSpecLoad_AbsoluteInt32) { + uint32 offset = Read32(); + BC_PRINT_OP("load32a %u, %s", offset, FoxBCEmitter::GetRegisterName(static_cast(op_reg))); + } +} + +void FoxBCPrinter::DoPush(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecPush_Int32) { + uint32 value = Read32(); + BC_PRINT_OP("push32 %u", value); + } + else if (op_spec == OpSpecPush_Reg32) { + uint16 reg = Read16(); + BC_PRINT_OP("push32r %s", FoxBCEmitter::GetRegisterName(static_cast(reg))); + } +} + +void FoxBCPrinter::DoPop(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == OpSpecPush_Int32) { + BC_PRINT_OP("pop32 %s", FoxBCEmitter::GetRegisterName(static_cast(op_reg))); + } +} + +void FoxBCPrinter::DoArith(char* s, uint8 op_base, uint8 op_spec) +{ + uint8 a_reg = mBytecode[mBytecodeIndex++]; + uint8 b_reg = mBytecode[mBytecodeIndex++]; + + if (op_spec == OpSpecArith_Add) { + BC_PRINT_OP("add32 %s, %s", FoxBCEmitter::GetRegisterName(static_cast(a_reg)), + FoxBCEmitter::GetRegisterName(static_cast(b_reg))); + } +} + +void FoxBCPrinter::DoSave(char* s, uint8 op_base, uint8 op_spec) +{ + // Save a imm32 into an offset in the stack + if (op_spec == OpSpecSave_Int32) { + const int16 offset = Read16(); + const uint32 value = Read32(); + + BC_PRINT_OP("save32 %d, %u", offset, value); + } + + // Save a register into an offset in the stack + else if (op_spec == OpSpecSave_Reg32) { + const int16 offset = Read16(); + uint16 reg = Read16(); + + BC_PRINT_OP("save32r %d, %s", offset, FoxBCEmitter::GetRegisterName(static_cast(reg))); + } + else if (op_spec == OpSpecSave_AbsoluteInt32) { + const uint32 offset = Read32(); + const uint32 value = Read32(); + + BC_PRINT_OP("save32a %u, %u", offset, value); + } + else if (op_spec == OpSpecSave_AbsoluteReg32) { + const uint32 offset = Read32(); + uint16 reg = Read16(); + + BC_PRINT_OP("save32ar %u, %s", offset, FoxBCEmitter::GetRegisterName(static_cast(reg))); + } +} + +void FoxBCPrinter::DoJump(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecJump_Relative) { + uint16 offset = Read16(); + printf("// jump relative to (%u)\n", mBytecodeIndex + offset); + BC_PRINT_OP("jmpr %d", offset); + } + else if (op_spec == OpSpecJump_Absolute) { + uint32 position = Read32(); + BC_PRINT_OP("jmpa %u", position); + } + else if (op_spec == OpSpecJump_AbsoluteReg32) { + uint16 reg = Read16(); + BC_PRINT_OP("jmpar %s", FoxBCEmitter::GetRegisterName(static_cast(reg))); + } + else if (op_spec == OpSpecJump_CallAbsolute) { + uint32 position = Read32(); + BC_PRINT_OP("calla %u", position); + } + else if (op_spec == OpSpecJump_ReturnToCaller) { + BC_PRINT_OP("ret"); + } + else if (op_spec == OpSpecJump_CallExternal) { + uint32 hashed_name = Read32(); + BC_PRINT_OP("callext %u", hashed_name); + } +} + +void FoxBCPrinter::DoData(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecData_String) { + uint16 length = Read16(); + char* data_str = FX_SCRIPT_ALLOC_MEMORY(char, length); + uint16* data_str16 = reinterpret_cast(data_str); + + uint32 bytecode_end = mBytecodeIndex + length; + int data_index = 0; + while (mBytecodeIndex < bytecode_end) { + uint16 value16 = Read16(); + data_str16[data_index++] = ((value16 << 8) | (value16 >> 8)); + } + + BC_PRINT_OP("datastr %d, %.*s", length, length, data_str); + + FX_SCRIPT_FREE(char, data_str); + } + else if (op_spec == OpSpecData_ParamsStart) { + BC_PRINT_OP("paramsstart"); + } +} + +void FoxBCPrinter::DoType(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecType_Int) { + BC_PRINT_OP("typeint"); + } + else if (op_spec == OpSpecType_String) { + BC_PRINT_OP("typestr"); + } +} + +void FoxBCPrinter::DoMove(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == OpSpecMove_Int32) { + uint32 value = Read32(); + BC_PRINT_OP("move32 %s, %u\t", FoxBCEmitter::GetRegisterName(static_cast(op_reg)), value); + } +} + + +void FoxBCPrinter::Print() +{ + while (mBytecodeIndex < mBytecode.Size()) { + PrintOp(); + } +} + +void FoxBCPrinter::PrintOp() +{ + uint32 bc_index = mBytecodeIndex; + + uint16 op_full = Read16(); + + const uint8 op_base = static_cast(op_full >> 8); + const uint8 op_spec = static_cast(op_full & 0xFF); + + char s[128]; + + switch (op_base) { + case OpBase_Push: + DoPush(s, op_base, op_spec); + break; + case OpBase_Pop: + DoPop(s, op_base, op_spec); + break; + case OpBase_Load: + DoLoad(s, op_base, op_spec); + break; + case OpBase_Arith: + DoArith(s, op_base, op_spec); + break; + case OpBase_Jump: + DoJump(s, op_base, op_spec); + break; + case OpBase_Save: + DoSave(s, op_base, op_spec); + break; + case OpBase_Data: + DoData(s, op_base, op_spec); + break; + case OpBase_Type: + DoType(s, op_base, op_spec); + break; + case OpBase_Move: + DoMove(s, op_base, op_spec); + break; + } + + printf("%-25s", s); + + printf("\t// Offset: %u\n", bc_index); +} + + +/////////////////////////////////////////// +// Bytecode VM +/////////////////////////////////////////// + +void FoxVM::PrintRegisters() +{ + printf("\n=== Register Dump ===\n\n"); + printf("X0=%u\tX1=%u\tX2=%u\tX3=%u\n", Registers[FX_REG_X0], Registers[FX_REG_X1], Registers[FX_REG_X2], Registers[FX_REG_X3]); + + printf("XR=%u\tRA=%u\n", Registers[FX_REG_XR], Registers[FX_REG_RA]); + + printf("\n=====================\n\n"); +} + +uint16 FoxVM::Read16() +{ + uint8 lo = mBytecode[mPC++]; + uint8 hi = mBytecode[mPC++]; + + return ((static_cast(lo) << 8) | hi); +} + +uint32 FoxVM::Read32() +{ + uint16 lo = Read16(); + uint16 hi = Read16(); + + return ((static_cast(lo) << 16) | hi); +} + +void FoxVM::Push16(uint16 value) +{ + uint16* dptr = reinterpret_cast(Stack + Registers[FX_REG_SP]); + (*dptr) = value; + + Registers[FX_REG_SP] += sizeof(uint16); +} + +void FoxVM::Push32(uint32 value) +{ + uint32* dptr = reinterpret_cast(Stack + Registers[FX_REG_SP]); + (*dptr) = value; + + Registers[FX_REG_SP] += sizeof(uint32); +} + +uint32 FoxVM::Pop32() +{ + if (Registers[FX_REG_SP] == 0) { + printf("ERR\n"); + } + + Registers[FX_REG_SP] -= sizeof(uint32); + + uint32 value = *reinterpret_cast(Stack + Registers[FX_REG_SP]); + return value; +} + + +FoxVMCallFrame& FoxVM::PushCallFrame() +{ + mIsInCallFrame = true; + + FoxVMCallFrame& start_frame = mCallFrames[mCallFrameIndex++]; + start_frame.StartStackIndex = Registers[FX_REG_SP]; + + return start_frame; +} + +void FoxVM::PopCallFrame() +{ + FoxVMCallFrame* frame = GetCurrentCallFrame(); + if (!frame) { + FX_BREAKPOINT; + } + + while (Registers[FX_REG_SP] > frame->StartStackIndex) { + Pop32(); + } + + --mCallFrameIndex; + + if (mCallFrameIndex == 0) { + mIsInCallFrame = false; + } +} + +FoxVMCallFrame* FoxVM::GetCurrentCallFrame() +{ + if (!mIsInCallFrame || mCallFrameIndex < 1) { + return nullptr; + } + + return &mCallFrames[mCallFrameIndex - 1]; +} + +FoxExternalFunc* FoxVM::FindExternalFunction(FoxHash hashed_name) +{ + for (FoxExternalFunc& func : mExternalFuncs) { + if (func.HashedName == hashed_name) { + return &func; + } + } + return nullptr; +} + +void FoxVM::ExecuteOp() +{ + uint16 op_full = Read16(); + + const uint8 op_base = static_cast(op_full >> 8); + const uint8 op_spec = static_cast(op_full & 0xFF); + + switch (op_base) { + case OpBase_Push: + DoPush(op_base, op_spec); + break; + case OpBase_Pop: + DoPop(op_base, op_spec); + break; + case OpBase_Load: + DoLoad(op_base, op_spec); + break; + case OpBase_Arith: + DoArith(op_base, op_spec); + break; + case OpBase_Jump: + DoJump(op_base, op_spec); + break; + case OpBase_Save: + DoSave(op_base, op_spec); + break; + case OpBase_Data: + DoData(op_base, op_spec); + break; + case OpBase_Type: + DoType(op_base, op_spec); + break; + case OpBase_Move: + DoMove(op_base, op_spec); + break; + } +} + +void FoxVM::DoLoad(uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == OpSpecLoad_Int32) { + int16 offset = Read16(); + + uint8* dataptr = &Stack[Registers[FX_REG_SP] + offset]; + uint32 data32 = *reinterpret_cast(dataptr); + + Registers[op_reg] = data32; + } + else if (op_spec == OpSpecLoad_AbsoluteInt32) { + uint32 offset = Read32(); + + uint8* dataptr = &Stack[offset]; + uint32 data32 = *reinterpret_cast(dataptr); + + Registers[op_reg] = data32; + } +} + +void FoxVM::DoPush(uint8 op_base, uint8 op_spec) +{ + if (mIsInParams) { + if (mCurrentType != FoxValue::NONETYPE) { + mPushedTypes.Insert(mCurrentType); + mCurrentType = FoxValue::NONETYPE; + } + else { + mPushedTypes.Insert(FoxValue::INT); + } + } + + if (op_spec == OpSpecPush_Int32) { + uint32 value = Read32(); + Push32(value); + } + else if (op_spec == OpSpecPush_Reg32) { + uint16 reg = Read16(); + Push32(Registers[reg]); + } +} + +void FoxVM::DoPop(uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == OpSpecPush_Int32) { + uint32 value = Pop32(); + + Registers[op_reg] = value; + } + + if (mIsInParams) { + mPushedTypes.RemoveLast(); + } +} + +void FoxVM::DoArith(uint8 op_base, uint8 op_spec) +{ + uint8 a_reg = mBytecode[mPC++]; + uint8 b_reg = mBytecode[mPC++]; + + if (op_spec == OpSpecArith_Add) { + Registers[FX_REG_XR] = Registers[a_reg] + Registers[b_reg]; + } +} + +void FoxVM::DoSave(uint8 op_base, uint8 op_spec) +{ + uint32 offset; + + if (op_spec == OpSpecSave_AbsoluteInt32 || op_spec == OpSpecSave_AbsoluteReg32) { + offset = Read32(); + } + else { + // The offset is relative to the stack pointer, get the absolute value + const int16 relative_offset = static_cast(Read16()); + offset = static_cast(Registers[FX_REG_SP] + relative_offset); + } + + uint32* dataptr = reinterpret_cast(&Stack[offset]); + + // Save a imm32 into an offset in the stack + if (op_spec == OpSpecSave_Int32) { + (*dataptr) = Read32(); + } + + // Save a register into an offset in the stack + else if (op_spec == OpSpecSave_Reg32) { + uint16 reg = Read16(); + (*dataptr) = Registers[reg]; + } + + else if (op_spec == OpSpecSave_AbsoluteInt32) { + (*dataptr) = Read32(); + } + + else if (op_spec == OpSpecSave_AbsoluteReg32) { + uint16 reg = Read16(); + (*dataptr) = Registers[reg]; + } +} + +void FoxVM::DoJump(uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecJump_Relative) { + uint16 offset = Read16(); + mPC += offset; + } + else if (op_spec == OpSpecJump_Absolute) { + uint32 position = Read32(); + mPC = position; + } + else if (op_spec == OpSpecJump_AbsoluteReg32) { + uint16 reg = Read16(); + mPC = Registers[reg]; + } + else if (op_spec == OpSpecJump_CallAbsolute) { + uint32 call_address = Read32(); + // printf("Call to address % 4u\n", call_address); + + // Push32(mPC); + + Registers[FX_REG_RA] = mPC; + + mPushedTypes.Clear(); + mIsInParams = false; + + PushCallFrame(); + + // Jump to the function address + mPC = call_address; + } + else if (op_spec == OpSpecJump_ReturnToCaller) { + PopCallFrame(); + + // uint32 return_address = Pop32(); + + uint32 return_address = Registers[FX_REG_RA]; + // printf("Return to caller (%04d)\n", return_address); + mPC = return_address; + + // Restore the return address register to its previous value. This is pushed when `paramsstart` is encountered. + // Registers[FX_REG_RA] = Pop32(); + } + else if (op_spec == OpSpecJump_CallExternal) { + uint32 hashed_name = Read32(); + + FoxExternalFunc* external_func = FindExternalFunction(hashed_name); + + if (!external_func) { + printf("!!! Could not find external function in VM!\n"); + return; + } + + std::vector params; + params.reserve(external_func->ParameterTypes.size()); + + uint32 num_params = mPushedTypes.Size(); + + printf("Num Params: %u\n", num_params); + + for (int i = 0; i < num_params; i++) { + FoxValue::ValueType param_type = mPushedTypes.GetLast(); + + FoxValue value; + value.Type = param_type; + + if (param_type == FoxValue::INT) { + value.ValueInt = Pop32(); + value.Type = param_type; + } + else if (param_type == FoxValue::STRING) { + uint32 string_location = Pop32(); + uint8* str_base_ptr = &mBytecode[string_location]; + // uint16 str_length = *((uint16*)str_base_ptr); + + str_base_ptr += 2; + + value.ValueString = reinterpret_cast(str_base_ptr); + value.Type = param_type; + } + + mPushedTypes.RemoveLast(); + + params.push_back(value); + } + + mPushedTypes.Clear(); + mIsInParams = false; + + FoxValue return_value {}; + external_func->Function(this, params, &return_value); + } +} + +void FoxVM::DoData(uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecData_String) { + uint16 length = Read16(); + mPC += length; + } + else if (op_spec == OpSpecData_ParamsStart) { + mIsInParams = true; + + // Push the current return address pointer. This is so nested function calls can correctly navigate back + // to the caller. + // Push32(Registers[FX_REG_RA]); + } +} + +void FoxVM::DoType(uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecType_Int) { + mCurrentType = FoxValue::INT; + } + else if (op_spec == OpSpecType_String) { + mCurrentType = FoxValue::STRING; + } +} + +void FoxVM::DoMove(uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + FoxBCRegister op_reg = static_cast(op_spec_raw & 0x0F); + + if (op_spec == OpSpecMove_Int32) { + int32 value = Read32(); + Registers[op_reg] = value; + } +} + + +////////////////////////////////////////////////// +// Script Bytecode to x86 Transpiler +////////////////////////////////////////////////// + +uint16 FoxTranspilerX86::Read16() +{ + uint8 lo = mBytecode[mBytecodeIndex++]; + uint8 hi = mBytecode[mBytecodeIndex++]; + + return ((static_cast(lo) << 8) | hi); +} + +uint32 FoxTranspilerX86::Read32() +{ + uint16 lo = Read16(); + uint16 hi = Read16(); + + return ((static_cast(lo) << 16) | hi); +} + +static const char* GetX86Register(FoxBCRegister reg) +{ + switch (reg) { + case FX_REG_X0: + return "eax"; + case FX_REG_X1: + return "ebx"; + case FX_REG_X2: + return "ecx"; + case FX_REG_X3: + return "edx"; + case FX_REG_SP: + return "esp"; + case FX_REG_RA: + return "esi"; + case FX_REG_XR: + return "eax"; + default:; + } + return "UNKNOWN"; +} + +// #define BC_PRINT_OP(fmt_, ...) snprintf(s, 128, fmt_, ##__VA_ARGS__) + +void FoxTranspilerX86::DoLoad(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == OpSpecLoad_Int32) { + int16 offset = Read16(); + // load32 [off32] [%r32] + offset += 8; + StrOut("mov %s, [ebp %c %d]", GetX86Register(static_cast(op_reg)), (offset <= 0 ? '+' : '-'), abs(offset)); + } + else if (op_spec == OpSpecLoad_AbsoluteInt32) { + uint32 offset = Read32(); + // StrOut("load32a %u, %s", offset, GetX86Register(static_cast(op_reg))); + StrOut("mov %s, [esi + %u]", GetX86Register(static_cast(op_reg)), offset); + } +} + +void FoxTranspilerX86::DoPush(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecPush_Int32) { + uint32 value = Read32(); + // push32 [imm32] + StrOut("push dword %u", value); + } + else if (op_spec == OpSpecPush_Reg32) { + uint16 reg = Read16(); + // push32r [%reg32] + StrOut("push %s", GetX86Register(static_cast(reg))); + } + + if (mIsInFunction) { + mSizePushedInFunction += 4; + } +} + +void FoxTranspilerX86::DoPop(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == OpSpecPop_Int32) { + // pop32 [%reg32] + StrOut("pop %s", GetX86Register(static_cast(op_reg))); + } +} + +void FoxTranspilerX86::DoArith(char* s, uint8 op_base, uint8 op_spec) +{ + uint8 a_reg = mBytecode[mBytecodeIndex++]; + uint8 b_reg = mBytecode[mBytecodeIndex++]; + + if (op_spec == OpSpecArith_Add) { + // add32 [%reg32] [%reg32] + StrOut("add %s, %s", GetX86Register(static_cast(a_reg)), GetX86Register(static_cast(b_reg))); + } +} + +void FoxTranspilerX86::DoSave(char* s, uint8 op_base, uint8 op_spec) +{ + // Save a imm32 into an offset in the stack + if (op_spec == OpSpecSave_Int32) { + const int16 offset = Read16() + 8; + const int32 value = Read32(); + + StrOut("mov dword [ebp %c %d], %d", (offset <= 0 ? '+' : '-'), abs(offset), value); + // BC_PRINT_OP("save32 %d, %u", offset, value); + } + + // Save a register into an offset in the stack + else if (op_spec == OpSpecSave_Reg32) { + int16 offset = Read16(); + uint16 reg = Read16(); + + offset += 8; + + StrOut("mov [ebp %c %d], %s", (offset <= 0 ? '+' : '-'), abs(offset), GetX86Register(static_cast(reg))); + // BC_PRINT_OP("save32r %d, %s", offset, GetX86Register(static_cast(reg))); + } + else if (op_spec == OpSpecSave_AbsoluteInt32) { + const uint32 offset = Read32(); + const int32 value = Read32(); + + // StrOut("save32a %u, %u", offset, value); + StrOut("mov [esi %c %d], %d", (offset <= 0 ? '+' : '-'), offset, value); + } + else if (op_spec == OpSpecSave_AbsoluteReg32) { + const uint32 offset = Read32(); + uint16 reg = Read16(); + + // StrOut("save32ar %u, %s", offset, GetX86Register(static_cast(reg))); + StrOut("mov [esi %c %d], %s", (offset <= 0 ? '+' : '-'), offset, GetX86Register(static_cast(reg))); + } +} + +void FoxTranspilerX86::DoJump(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecJump_Relative) { + uint16 offset = Read16(); + + mIsInFunction = true; + + StrOut("jmp _A_%u", mBytecodeIndex + offset); + + if (mTextIndent) { + --mTextIndent; + } + StrOut("_L_%u:", mBytecodeIndex); + + ++mTextIndent; + + + StrOut("pop esi ; save return address"); + StrOut("mov ebp, esp"); + + StrOut(""); + } + else if (op_spec == OpSpecJump_Absolute) { + uint32 position = Read32(); + StrOut("jmpa %u", position); + } + else if (op_spec == OpSpecJump_AbsoluteReg32) { + uint16 reg = Read16(); + StrOut("jmpar %s", GetX86Register(static_cast(reg))); + } + else if (op_spec == OpSpecJump_CallAbsolute) { + uint32 position = Read32(); + // BC_PRINT_OP("calla %u", position); + StrOut("call _L_%u", position); + } + else if (op_spec == OpSpecJump_ReturnToCaller) { + StrOut("add esp, %u", mSizePushedInFunction); + mSizePushedInFunction = 0; + mIsInFunction = false; + + StrOut(""); + StrOut("push esi ; push return address"); + StrOut("ret"); + + --mTextIndent; + + StrOut("_A_%u:", mBytecodeIndex); + + ++mTextIndent; + } + else if (op_spec == OpSpecJump_CallExternal) { + uint32 hashed_name = Read32(); + // StrOut("callext %u", hashed_name); + + StrOut("nop ; callext %u", hashed_name); + } +} + +void FoxTranspilerX86::DoData(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecData_String) { + uint16 length = Read16(); + char* data_str = FX_SCRIPT_ALLOC_MEMORY(char, length); + uint16* data_str16 = reinterpret_cast(data_str); + + uint32 bytecode_end = mBytecodeIndex + length; + int data_index = 0; + while (mBytecodeIndex < bytecode_end) { + uint16 value16 = Read16(); + data_str16[data_index++] = ((value16 << 8) | (value16 >> 8)); + } + + StrOut("datastr %d, %.*s", length, length, data_str); + + FX_SCRIPT_FREE(char, data_str); + } + else if (op_spec == OpSpecData_ParamsStart) { + StrOut("; Parameters start"); + // BC_PRINT_OP("paramsstart"); + } +} + +void FoxTranspilerX86::DoType(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == OpSpecType_Int) { + // BC_PRINT_OP("typeint"); + } + else if (op_spec == OpSpecType_String) { + // BC_PRINT_OP("typestr"); + } +} + +void FoxTranspilerX86::DoMove(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + FoxBCRegister op_reg = static_cast(op_spec_raw & 0x0F); + + if (op_spec == OpSpecMove_Int32) { + int32 value = Read32(); + // StrOut("move32 %s, %u", FoxBCEmitter::GetRegisterName(op_reg), value); + StrOut("mov %s, %d", GetX86Register(op_reg), value); + } +} + + +void FoxTranspilerX86::Print() +{ + // Print header + StrOut("section .text"); + StrOut("global _start"); + StrOut("_start:"); + + + while (mBytecodeIndex < mBytecode.Size()) { + PrintOp(); + } + + // Print footer + + StrOut("mov ebx, eax"); + StrOut("mov eax, 1"); + StrOut("int 0x80"); +} + +#include + +void FoxTranspilerX86::StrOut(const char* fmt, ...) +{ + for (int i = 0; i < mTextIndent; i++) { + putchar('\t'); + } + + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + puts(""); +} + +void FoxTranspilerX86::PrintOp() +{ + // uint32 bc_index = mBytecodeIndex; + + uint16 op_full = Read16(); + + const uint8 op_base = static_cast(op_full >> 8); + const uint8 op_spec = static_cast(op_full & 0xFF); + + char s[128]; + + switch (op_base) { + case OpBase_Push: + DoPush(s, op_base, op_spec); + break; + case OpBase_Pop: + DoPop(s, op_base, op_spec); + break; + case OpBase_Load: + DoLoad(s, op_base, op_spec); + break; + case OpBase_Arith: + DoArith(s, op_base, op_spec); + break; + case OpBase_Jump: + DoJump(s, op_base, op_spec); + break; + case OpBase_Save: + DoSave(s, op_base, op_spec); + break; + case OpBase_Data: + DoData(s, op_base, op_spec); + break; + case OpBase_Type: + DoType(s, op_base, op_spec); + break; + case OpBase_Move: + DoMove(s, op_base, op_spec); + break; + } + // printf("%-25s\n", s); +} + + +/////////////////////////////////////////////// +// IR Asm Emitter +/////////////////////////////////////////////// + +#pragma region IrEmitter + +void FoxIREmitter::BeginEmitting(FoxAstNode* node) +{ + mStackSize = 1024; + + /*mStack = new uint8[1024]; + mStackStart = mStack;*/ + + mBytecode.Create(4096); + VarHandles.Create(64); + + Emit(node); + + printf("\n"); + + PrintBytecode(); +} + +#define RETURN_IF_NO_NODE(node_) \ + if ((node_) == nullptr) { \ + return; \ + } + +#define RETURN_VALUE_IF_NO_NODE(node_, value_) \ + if ((node_) == nullptr) { \ + return (value_); \ + } + +void FoxIREmitter::Emit(FoxAstNode* node) +{ + RETURN_IF_NO_NODE(node); + + if (node->NodeType == FX_AST_BLOCK) { + return EmitBlock(reinterpret_cast(node)); + } + // else if (node->NodeType == FX_AST_ACTIONDECL) { + // return EmitFunction(reinterpret_cast(node)); + // } + else if (node->NodeType == FX_AST_ACTIONCALL) { + return DoFunctionCall(reinterpret_cast(node)); + } + else if (node->NodeType == FX_AST_ASSIGN) { + return EmitAssign(reinterpret_cast(node)); + } + else if (node->NodeType == FX_AST_VARDECL) { + DoVarDeclare(reinterpret_cast(node)); + return; + } + else if (node->NodeType == FX_AST_RETURN) { + FoxAstReturn* return_node = reinterpret_cast(node); + + // Is there a return value provided? + if (return_node->Rhs) { + FoxAstNode* return_rhs = return_node->Rhs; + + // Check to see if its a literal + if (return_rhs->NodeType == FX_AST_LITERAL) { + FoxAstLiteral* literal = reinterpret_cast(return_rhs); + + if (literal->Value.Type == FoxValue::INT) { + EmitJumpReturnToCallerInt32(literal->Value.ValueInt); + } + else if (literal->Value.Type == FoxValue::REF) { + const FoxAstVarRef* var_ref = literal->Value.ValueRef; + + // Get the variable and load it into a register. + FoxBytecodeVarHandle* var_to_return = FindVarHandle(var_ref->Name->GetHash()); + + FoxIRRegister var_to_return_reg = FX_IR_REG_RETURN_VALUE; + + MARK_REGISTER_USED(var_to_return_reg); + + if (var_to_return) { + EmitVariableGetInt32(var_to_return->VarIndexInScope, var_to_return_reg); + } + + // if (var_to_return) { + // DoLoad(var_to_return->Offset, var_to_return_reg); + // } + + // Free the register as we will not need it after scope end + MARK_REGISTER_FREE(var_to_return_reg); + + // Return with the register + EmitJumpReturnToCallerReg32(var_to_return_reg); + } + } + + return; + } + + // constexpr FoxHash return_val_hash = FoxHashStr(FX_SCRIPT_VAR_RETURN_VAL); + + // FoxBytecodeVarHandle* return_var = FindVarHandle(return_val_hash); + + // FoxIRRegister result_register = FindFreeReg32(); + // MARK_REGISTER_USED(result_register); + + // if (return_var) { + // DoLoad(return_var->Offset, result_register); + // } + + // MARK_REGISTER_FREE(result_register); + + EmitJumpReturnToCaller(); + + return; + } +} + +FoxBytecodeVarHandle* FoxIREmitter::FindVarHandle(FoxHash hashed_name) +{ + for (FoxBytecodeVarHandle& handle : VarHandles) { + if (handle.HashedName == hashed_name) { + return &handle; + } + } + return nullptr; +} + +FoxBytecodeFunctionHandle* FoxIREmitter::FindFunctionHandle(FoxHash hashed_name) +{ + for (FoxBytecodeFunctionHandle& handle : FunctionHandles) { + if (handle.HashedName == hashed_name) { + return &handle; + } + } + return nullptr; +} + + +FoxIRRegister FoxIREmitter::FindFreeReg32() +{ + for (int register_index = FX_IR_GW0; register_index <= FX_IR_GW3; register_index++) { + uint32 gp_r = (1 << register_index); + + if (!(mRegsInUse & gp_r)) { + return static_cast(register_index); + } + } + + return FX_IR_GW3; +} + +FoxIRRegister FoxIREmitter::FindFreeReg64() +{ + for (int register_index = FX_IR_GX0; register_index <= FX_IR_GX3; register_index++) { + uint32 gp_r = (1 << register_index); + + if (!(mRegsInUse & gp_r)) { + return static_cast(register_index); + } + } + + return FX_IR_GX3; +} + +const char* FoxIREmitter::GetRegisterName(FoxIRRegister reg) +{ + switch (reg) { + case FX_IR_GW0: + return "GW0"; + case FX_IR_GW1: + return "GW1"; + case FX_IR_GW2: + return "GW2"; + case FX_IR_GW3: + return "GW3"; + case FX_IR_GX0: + return "GX0"; + case FX_IR_GX1: + return "GX1"; + case FX_IR_GX2: + return "GX2"; + case FX_IR_GX3: + return "GX3"; + case FX_IR_SP: + return "SP"; + case FX_IR_REG_RETURN_VALUE: + return "RETVAL"; + default:; + }; + + return "NONE"; +} + +void FoxIREmitter::Write16(uint16 value) +{ + mBytecode.Insert(static_cast(value >> 8)); + mBytecode.Insert(static_cast(value)); +} + +void FoxIREmitter::Write32(uint32 value) +{ + Write16(static_cast(value >> 16)); + Write16(static_cast(value)); +} + +void FoxIREmitter::WriteOp(uint8 op_base, uint8 op_spec) +{ + mBytecode.Insert(op_base); + mBytecode.Insert(op_spec); +} + +using IRRhsMode = FoxIREmitter::RhsMode; + +#define MARK_REGISTER_USED(regn_) \ + { \ + MarkRegisterUsed(regn_); \ + } +#define MARK_REGISTER_FREE(regn_) \ + { \ + MarkRegisterFree(regn_); \ + } + +void FoxIREmitter::MarkRegisterUsed(FoxIRRegister reg) +{ + uint16 register_flag = (1 << reg); + mRegsInUse = static_cast(uint16(mRegsInUse) | register_flag); +} + +void FoxIREmitter::MarkRegisterFree(FoxIRRegister reg) +{ + uint16 register_flag = (1 << reg); + mRegsInUse = static_cast(uint16(mRegsInUse) & (~register_flag)); +} + +void FoxIREmitter::EmitSave32(int16 offset, uint32 value) +{ + // SAVE32 [i16 offset] [i32] + WriteOp(IrBase_Save, IrSpecSave_Int32); + + Write16(offset); + Write32(value); +} + +void FoxIREmitter::EmitSaveReg32(int16 offset, FoxIRRegister reg) +{ + // SAVE32r [i16 offset] [%r32] + WriteOp(IrBase_Save, IrSpecSave_Reg32); + + Write16(offset); + Write16(reg); +} + + +void FoxIREmitter::EmitSaveAbsolute32(uint32 position, uint32 value) +{ + // SAVE32a [i32 offset] [i32] + WriteOp(IrBase_Save, IrSpecSave_AbsoluteInt32); + + Write32(position); + Write32(value); +} + +void FoxIREmitter::EmitSaveAbsoluteReg32(uint32 position, FoxIRRegister reg) +{ + // SAVE32r [i32 offset] [%r32] + WriteOp(IrBase_Save, IrSpecSave_AbsoluteReg32); + + Write32(position); + Write16(reg); +} + +void FoxIREmitter::EmitPush32(uint32 value) +{ + // PUSH32 [i32] + WriteOp(IrBase_Push, IrSpecPush_Int32); + Write32(value); + + mStackOffset += 4; +} + +void FoxIREmitter::EmitPush32r(FoxIRRegister reg) +{ + // PUSH32r [%r32] + WriteOp(IrBase_Push, IrSpecPush_Reg32); + Write16(reg); + + mStackOffset += 4; +} + +void FoxIREmitter::EmitStackAlloc(uint16 size) +{ + // SALLOC [u16] + + WriteOp(IrBase_Push, IrSpecPush_StackAlloc); + Write16(size); + + mStackOffset += size; +} + + +void FoxIREmitter::EmitPop32(FoxIRRegister output_reg) +{ + // POP32 [%r32] + WriteOp(IrBase_Pop, (IrSpecPop_Int32 << 4) | (output_reg & 0x0F)); + + mStackOffset -= 4; +} + +void FoxIREmitter::EmitLoad32(int offset, FoxIRRegister output_reg) +{ + // LOAD [i16] [%r32] + WriteOp(IrBase_Load, (IrSpecLoad_Int32 << 4) | (output_reg & 0x0F)); + Write16(static_cast(offset)); +} + +void FoxIREmitter::EmitLoadAbsolute32(uint32 position, FoxIRRegister output_reg) +{ + // LOADA [i32] [%r32] + WriteOp(IrBase_Load, (IrSpecLoad_AbsoluteInt32 << 4) | (output_reg & 0x0F)); + Write32(position); +} + +void FoxIREmitter::EmitJumpRelative(uint16 offset) +{ + WriteOp(IrBase_Jump, IrSpecJump_Relative); + Write16(offset); +} + +void FoxIREmitter::EmitJumpAbsolute(uint32 position) +{ + WriteOp(IrBase_Jump, IrSpecJump_Absolute); + Write32(position); +} + + +void FoxIREmitter::EmitJumpAbsoluteReg32(FoxIRRegister reg) +{ + WriteOp(IrBase_Jump, IrSpecJump_AbsoluteReg32); + Write16(reg); +} + +void FoxIREmitter::EmitJumpCallAbsolute(uint32 position) +{ + WriteOp(IrBase_Jump, IrSpecJump_CallAbsolute); + Write32(position); +} + + +void FoxIREmitter::EmitJumpCallExternal(FoxHash hashed_name) +{ + WriteOp(IrBase_Jump, IrSpecJump_CallExternal); + Write32(hashed_name); +} + +void FoxIREmitter::EmitJumpReturnToCaller() +{ + WriteOp(IrBase_Jump, IrSpecJump_ReturnToCaller); +} + +void FoxIREmitter::EmitJumpReturnToCallerReg32(FoxIRRegister reg) +{ + WriteOp(IrBase_Jump, IrSpecJump_ReturnToCaller_Reg32); + Write16(reg); +} + +void FoxIREmitter::EmitJumpReturnToCallerInt32(int32 value) +{ + WriteOp(IrBase_Jump, IrSpecJump_ReturnToCaller_Int32); + Write32(value); +} + +void FoxIREmitter::EmitMoveInt32(FoxIRRegister reg, uint32 value) +{ + WriteOp(IrBase_Move, (IrSpecMove_Int32 << 4) | (reg & 0x0F)); + Write32(value); +} + +void FoxIREmitter::EmitVariableSetInt32(uint16 var_index, int32 value) +{ + WriteOp(IrBase_Variable, IrSpecVariable_Set_Int32); + Write16(var_index); + Write32(value); +} + +void FoxIREmitter::EmitVariableSetReg32(uint16 var_index, FoxIRRegister reg) +{ + WriteOp(IrBase_Variable, IrSpecVariable_Set_Reg32); + Write16(var_index); + Write16(reg); +} + +void FoxIREmitter::EmitVariableGetInt32(uint16 var_index, FoxIRRegister dest_reg) +{ + WriteOp(IrBase_Variable, IrSpecVariable_Get_Int32); + Write16(var_index); + Write16(dest_reg); +} + +void FoxIREmitter::EmitParamsStart() +{ + WriteOp(IrBase_Marker, IrSpecMarker_ParamsBegin); +} + +void FoxIREmitter::EmitType(FoxValue::ValueType type) +{ + IrSpecType op_type = IrSpecType_Int; + + if (type == FoxValue::STRING) { + op_type = IrSpecType_String; + } + + WriteOp(IrBase_Type, op_type); +} + +uint32 FoxIREmitter::EmitDataString(char* str, uint16 length) +{ + WriteOp(IrBase_Data, IrSpecData_String); + + uint32 start_index = mBytecode.Size(); + + uint16 final_length = length + 1; + + // If the length is not a factor of 2 (sizeof uint16) then add a byte of padding + if ((final_length & 0x01)) { + ++final_length; + } + + Write16(final_length); + + for (int i = 0; i < final_length; i += 2) { + mBytecode.Insert(str[i]); + + if (i >= length) { + mBytecode.Insert(0); + break; + } + + mBytecode.Insert(str[i + 1]); + } + + return start_index; +} + + +FoxIRRegister FoxIREmitter::EmitBinop(FoxAstBinop* binop, FoxBytecodeVarHandle* handle) +{ + bool will_preserve_lhs = false; + // Load the A and B values into the registers + FoxIRRegister a_reg = EmitRhs(binop->Left, RhsMode::RHS_FETCH_TO_REGISTER, handle); + + // Since there is a chance that this register will be clobbered (by binop, function call, etc), we will + // push the value of the register here and return it after processing the RHS + if (binop->Right->NodeType != FX_AST_LITERAL) { + will_preserve_lhs = true; + EmitPush32r(a_reg); + } + + FoxIRRegister b_reg = EmitRhs(binop->Right, RhsMode::RHS_FETCH_TO_REGISTER, handle); + + // Retrieve the previous LHS + if (will_preserve_lhs) { + EmitPop32(a_reg); + } + + if (binop->OpToken->Type == TT::Plus) { + WriteOp(IrBase_Arith, IrSpecArith_Add_Reg32); + + mBytecode.Insert(a_reg); + mBytecode.Insert(b_reg); + } + + // We no longer need the lhs or rhs registers, free em + // MARK_REGISTER_FREE(a_reg); + MARK_REGISTER_FREE(b_reg); + + return a_reg; +} + +FoxIRRegister FoxIREmitter::EmitVarFetch(FoxAstVarRef* ref, RhsMode mode) +{ + FoxBytecodeVarHandle* var_handle = FindVarHandle(ref->Name->GetHash()); + + // bool force_absolute_load = false; + + // If the variable is from a previous scope, load it from an absolute address. local offsets + // will change depending on where they are called. + // if (var_handle->ScopeIndex < mScopeIndex) { + // force_absolute_load = true; + // } + + if (!var_handle) { + printf("Could not find var handle!"); + return FX_IR_GW0; + } + + FoxIRRegister reg = FindFreeReg32(); + + MARK_REGISTER_USED(reg); + + // DoLoad(var_handle->Offset, reg, force_absolute_load); + EmitVariableGetInt32(var_handle->VarIndexInScope, reg); + + if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { + return reg; + } + + // If we are just copying the variable to this new variable, we can free the register after + // we push to the stack. + if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { + if (var_handle->Type == FoxValue::STRING) { + EmitType(var_handle->Type); + } + + // Save the value register to the new variable + + EmitVariableSetReg32(var_handle->VarIndexInScope, reg); + // EmitPush32r(reg); + MARK_REGISTER_FREE(reg); + + return FX_IR_GW0; + } + + return reg; +} + + +uint16 FoxIREmitter::GetSizeOfType(FoxTokenizer::Token* token) +{ + const FoxHash type_hash = token->GetHash(); + + constexpr FoxHash type_int_hash = FoxHashStr("int"); + constexpr FoxHash type_float_hash = FoxHashStr("float"); + + if (type_hash == type_int_hash) { + return sizeof(int32); + } + else if (type_hash == type_float_hash) { + return sizeof(float32); + } + else { + printf("!!! UNKNOWN TYPE\n"); + } + + return 0; +} + + +void FoxIREmitter::DoLoad(uint32 stack_offset, FoxIRRegister output_reg, bool force_absolute) +{ + if (stack_offset < 0xFFFE && !force_absolute) { + // Relative load + + // Calculate the relative index to the current stack offset + int input_offset = -(static_cast(mStackOffset) - static_cast(stack_offset)); + + EmitLoad32(input_offset, output_reg); + } + else { + // Absolute load + EmitLoadAbsolute32(stack_offset, output_reg); + } +} + +void FoxIREmitter::DoSaveInt32(uint32 stack_offset, uint32 value, bool force_absolute) +{ + if (stack_offset < 0xFFFE && !force_absolute) { + // Relative save + + // Calculate the relative index to the current stack offset + int input_offset = -(static_cast(mStackOffset) - static_cast(stack_offset)); + + EmitSave32(input_offset, value); + } + else { + // Absolute save + EmitSaveAbsolute32(stack_offset, value); + } +} + +void FoxIREmitter::DoSaveReg32(uint32 stack_offset, FoxIRRegister reg, bool force_absolute) +{ + if (stack_offset < 0xFFFE && !force_absolute) { + // Relative save + + // Calculate the relative index to the current stack offset + int input_offset = -(static_cast(mStackOffset) - static_cast(stack_offset)); + + EmitSaveReg32(input_offset, reg); + } + else { + // Absolute save + EmitSaveAbsoluteReg32(stack_offset, reg); + } +} + +void FoxIREmitter::EmitAssign(FoxAstAssign* assign) +{ + FoxBytecodeVarHandle* var_handle = FindVarHandle(assign->Var->Name->GetHash()); + if (var_handle == nullptr) { + printf("!!! Var '%.*s' does not exist!\n", assign->Var->Name->Length, assign->Var->Name->Start); + return; + } + + // bool force_absolute_save = false; + + // if (var_handle->ScopeIndex < mScopeIndex) { + // force_absolute_save = true; + // } + + // int output_offset = -(static_cast(mStackOffset) - static_cast(var_handle->Offset)); + + if (!var_handle) { + printf("Could not find var handle to assign to!"); + return; + } + + EmitRhs(assign->Rhs, RhsMode::RHS_ASSIGN_TO_HANDLE, var_handle); +} + +FoxIRRegister FoxIREmitter::EmitLiteralInt(FoxAstLiteral* literal, RhsMode mode, FoxBytecodeVarHandle* handle) +{ + // If this is on variable definition, push the value to the stack. + if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { + EmitPush32(literal->Value.ValueInt); + + return FX_IR_GW3; + } + + // If this is as a literal, push the value to the stack and pop onto the target register. + else if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { + // EmitPush32(literal->Value.ValueInt); + + FoxIRRegister output_reg = FindFreeReg32(); + // EmitPop32(output_reg); + + EmitMoveInt32(output_reg, literal->Value.ValueInt); + + // Mark the output register as used to store it + MARK_REGISTER_USED(output_reg); + + return output_reg; + } + + else if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { + // const bool force_absolute_save = (handle->ScopeIndex < mScopeIndex); + // DoSaveInt32(handle->Offset, literal->Value.ValueInt, force_absolute_save); + EmitVariableSetInt32(handle->VarIndexInScope, literal->Value.ValueInt); + + return FX_IR_GW3; + } + + return FX_IR_GW3; +} + + +FoxIRRegister FoxIREmitter::EmitLiteralString(FoxAstLiteral* literal, RhsMode mode, FoxBytecodeVarHandle* handle) +{ + const uint32 string_length = strlen(literal->Value.ValueString); + + // Emit the length and string data + const uint32 string_position = EmitDataString(literal->Value.ValueString, string_length); + + // local string some_value = "Some String"; + if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { + // Push the location and mark it as a pointer to a string + EmitType(FoxValue::STRING); + EmitPush32(string_position); + + return FX_IR_GW3; + } + + // some_function("Some String") + else if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { + // Push the location for the string and pop it back to a register. + EmitType(FoxValue::STRING); + + // Push the string position + // EmitPush32(string_position); + + // Find a register to output to and write the index + FoxIRRegister output_reg = FindFreeReg32(); + // EmitPop32(output_reg); + + EmitMoveInt32(output_reg, string_position); + + // Mark the output register as used to store it + MARK_REGISTER_USED(output_reg); + + return output_reg; + } + + // some_previous_value = "Some String"; + else if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { + const bool force_absolute_save = (handle->ScopeIndex < mScopeIndex); + + DoSaveInt32(handle->Offset, string_position, force_absolute_save); + handle->Type = FoxValue::STRING; + + return FX_IR_GW3; + } + + return FX_IR_GW3; +} + +void FoxIREmitter::EmitMarker(IrSpecMarker spec) +{ + WriteOp(IrBase_Marker, spec); +} + +FoxIRRegister FoxIREmitter::EmitRhs(FoxAstNode* rhs, FoxIREmitter::RhsMode mode, FoxBytecodeVarHandle* handle) +{ + if (rhs->NodeType == FX_AST_LITERAL) { + FoxAstLiteral* literal = reinterpret_cast(rhs); + + if (literal->Value.Type == FoxValue::INT) { + return EmitLiteralInt(literal, mode, handle); + } + else if (literal->Value.Type == FoxValue::STRING) { + return EmitLiteralString(literal, mode, handle); + } + else if (literal->Value.Type == FoxValue::REF) { + // Reference another value, load from memory into register + FoxIRRegister output_register = EmitVarFetch(literal->Value.ValueRef, mode); + if (mode == IRRhsMode::RHS_ASSIGN_TO_HANDLE) { + // DoSaveReg32(handle->Offset, output_register); + // + EmitVariableSetReg32(handle->VarIndexInScope, output_register); + } + + return output_register; + } + + return FX_IR_GW3; + } + + else if (rhs->NodeType == FX_AST_ACTIONCALL || rhs->NodeType == FX_AST_BINOP) { + FoxIRRegister result_register = FX_IR_GW3; + + if (rhs->NodeType == FX_AST_BINOP) { + result_register = EmitBinop(reinterpret_cast(rhs), handle); + } + + else if (rhs->NodeType == FX_AST_ACTIONCALL) { + DoFunctionCall(reinterpret_cast(rhs)); + // Function results are stored in XR + result_register = FX_IR_REG_RETURN_VALUE; + } + + + if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { + uint32 offset = mStackOffset; + EmitPush32r(result_register); + + MARK_REGISTER_FREE(result_register); + + if (handle) { + handle->Offset = offset; + } + + return FX_IR_GW3; + } + + else if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { + // Push the result to a register + EmitPush32r(result_register); + + MARK_REGISTER_FREE(result_register); + + // Find a register to output to, and pop the value to there. + FoxIRRegister output_reg = FindFreeReg32(); + EmitPop32(output_reg); + + // Mark the output register as used to store it + MARK_REGISTER_USED(output_reg); + + return output_reg; + } + else if (mode == IRRhsMode::RHS_ASSIGN_TO_HANDLE) { + // const bool force_absolute_save = (handle->ScopeIndex < mScopeIndex); + + // Save the value back to the variable + // DoSaveReg32(handle->Offset, result_register, force_absolute_save); + + EmitVariableSetReg32(handle->VarIndexInScope, result_register); + MARK_REGISTER_FREE(result_register); + + return FX_IR_GW3; + } + } + + return FX_IR_GW3; +} + +FoxBytecodeVarHandle* FoxIREmitter::DoVarDeclare(FoxAstVarDecl* decl, VarDeclareMode mode) +{ + RETURN_VALUE_IF_NO_NODE(decl, nullptr); + + // const uint16 size_of_type = static_cast(sizeof(int32)); + + const FoxHash type_int = FoxHashStr("int"); + const FoxHash type_string = FoxHashStr("string"); + + FoxHash decl_hash = decl->Name->GetHash(); + FoxHash type_hash = decl->Type->GetHash(); + + FoxValue::ValueType value_type = FoxValue::INT; + + switch (type_hash) { + case type_int: + value_type = FoxValue::INT; + break; + case type_string: + value_type = FoxValue::STRING; + break; + }; + + const uint16 size_of_type = GetSizeOfType(decl->Type); + + FoxBytecodeVarHandle handle { + .HashedName = decl_hash, + .Type = value_type, // Just int for now + .Offset = (mStackOffset), + .SizeOnStack = size_of_type, + .ScopeIndex = mScopeIndex, + }; + + VarHandles.Insert(handle); + + // FoxBytecodeVarHandle* inserted_handle = &VarHandles[VarHandles.Size() - 1]; + + FoxBytecodeVarHandle* var_handle = FindVarHandle(decl_hash); + + if (var_handle == nullptr) { + printf("!!! Could not find var handle!\n"); + return nullptr; + } + + if (mode == DECLARE_NO_EMIT) { + // Do not emit any values + return var_handle; + } + + + if (decl->Assignment) { + FoxAstNode* rhs = decl->Assignment->Rhs; + + EmitRhs(rhs, RhsMode::RHS_ASSIGN_TO_HANDLE, var_handle); + + + // EmitPush32(0); + + // EmitRhs(rhs, RhsMode::RHS_ASSIGN_TO_HANDLE, inserted_handle); + } + else { + // There is no assignment, push zero as the value for now and + // a later assignment can set it using save32. + EmitPush32(0); + } + + return var_handle; +} + +void FoxIREmitter::DoFunctionCall(FoxAstFunctionCall* call) +{ + RETURN_IF_NO_NODE(call); + + FoxBytecodeFunctionHandle* handle = FindFunctionHandle(call->HashedName); + + std::vector call_locations; + call_locations.reserve(8); + + // Push all params to stack + for (FoxAstNode* param : call->Params) { + // FoxRegister reg = + if (param->NodeType == FX_AST_ACTIONCALL) { + EmitRhs(param, RhsMode::RHS_DEFINE_IN_MEMORY, nullptr); + call_locations.push_back(mStackOffset - 4); + } + // MARK_REGISTER_FREE(reg); + } + + // EmitPush32r(FX_REG_RA); + + EmitParamsStart(); + + int call_location_index = 0; + + // Push all params to stack + for (FoxAstNode* param : call->Params) { + if (param->NodeType == FX_AST_ACTIONCALL) { + FoxIRRegister temp_register = FindFreeReg32(); + + DoLoad(call_locations[call_location_index], temp_register); + call_location_index++; + + EmitPush32r(temp_register); + + continue; + } + + EmitRhs(param, RhsMode::RHS_DEFINE_IN_MEMORY, nullptr); + } + + // The handle could not be found, write it as a possible external symbol. + if (!handle) { + printf("Call name-> %u\n", call->HashedName); + + // Since popping the parameters are handled internally in the VM, + // we need to decrement the stack offset here. + for (int i = 0; i < call->Params.size(); i++) { + mStackOffset -= 4; + } + + EmitJumpCallExternal(call->HashedName); + + // EmitPop32(FX_REG_RA); + return; + } + + EmitJumpCallAbsolute(handle->BytecodeIndex); + + // EmitPop32(FX_REG_RA); +} + +FoxBytecodeVarHandle* FoxIREmitter::DefineAndFetchParam(FoxAstNode* param_decl_node) +{ + if (param_decl_node->NodeType != FX_AST_VARDECL) { + printf("!!! param node type is not vardecl!\n"); + return nullptr; + } + + // Emit variable without emitting pushes or pops + FoxBytecodeVarHandle* handle = DoVarDeclare(reinterpret_cast(param_decl_node), DECLARE_NO_EMIT); + + if (!handle) { + printf("!!! could not define and fetch param!\n"); + return nullptr; + } + + assert(handle->SizeOnStack == 4); + + mStackOffset += handle->SizeOnStack; + + return handle; +} + +FoxBytecodeVarHandle* FoxIREmitter::DefineReturnVar(FoxAstVarDecl* decl) +{ + RETURN_VALUE_IF_NO_NODE(decl, nullptr); + + return DoVarDeclare(decl); +} + +void FoxIREmitter::EmitFunctionDefinitionsInBlock(FoxAstBlock* block) +{ + for (FoxAstNode* stmt : block->Statements) { + if (stmt->NodeType == FX_AST_ACTIONDECL) { + EmitFunction(reinterpret_cast(stmt)); + } + } +} + +void FoxIREmitter::EmitFunction(FoxAstFunctionDecl* function) +{ + RETURN_IF_NO_NODE(function); + + EmitFunctionDefinitionsInBlock(function->Block); + + ++mScopeIndex; + + // Store the bytecode offset before the function is emitted + + const size_t start_of_function = mBytecode.Size(); + printf("Start of function %zu\n", start_of_function); + + // Emit the jump instruction, we will update the jump position after emitting all of the code inside the block + // EmitJumpRelative(0); + + // const uint32 initial_stack_offset = mStackOffset; + + // const size_t header_jump_start_index = start_of_function + sizeof(uint16); + + size_t start_var_handle_count = VarHandles.Size(); + + // Offset for the pushed return address + mStackOffset += 4; + + // Emit the body of the function + { + for (FoxAstNode* param_decl_node : function->Params->Statements) { + DefineAndFetchParam(param_decl_node); + } + + // FoxBytecodeVarHandle* return_var = DefineReturnVar(function->ReturnVar); + + // Do not check if there are function definitions to be declared when emitting the block here as they are checked above, before any parameters + // or stack allocations are output. + EmitBlock(function->Block, true); + + // Check to see if there has been a return statement in the function + bool block_has_return = false; + + for (FoxAstNode* statement : function->Block->Statements) { + if (statement->NodeType == FX_AST_RETURN) { + block_has_return = true; + break; + } + } + + // There is no return statement in the function's block, add a return statement + if (!block_has_return) { + EmitJumpReturnToCaller(); + + // FoxIRRegister result_register = FindFreeReg32(); + + // MARK_REGISTER_USED(result_register); + + // if (return_var != nullptr) { + // DoLoad(return_var->Offset, result_register); + // } + } + } + + // Return offset back to pre-call + mStackOffset -= 4; + + // const size_t end_of_function = mBytecode.Size(); + // const uint16 distance_to_function = static_cast(end_of_function - (start_of_function)-4); + + // Update the jump to the end of the function + // mBytecode[header_jump_start_index] = static_cast(distance_to_function >> 8); + // mBytecode[header_jump_start_index + 1] = static_cast((distance_to_function & 0xFF)); + + FoxBytecodeFunctionHandle function_handle {.HashedName = function->Name->GetHash(), .BytecodeIndex = static_cast(start_of_function)}; + + const size_t number_of_scope_var_handles = VarHandles.Size() - start_var_handle_count; + printf("Number of var handles to remove: %zu\n", number_of_scope_var_handles); + + FunctionHandles.push_back(function_handle); + + --mScopeIndex; + + // Delete the variables on the stack + for (int i = 0; i < number_of_scope_var_handles; i++) { + FoxBytecodeVarHandle* var = VarHandles.RemoveLast(); + assert(var->SizeOnStack == 4); + mStackOffset -= var->SizeOnStack; + } +} + +void FoxIREmitter::EmitBlock(FoxAstBlock* block, bool ignore_function_definitions) +{ + RETURN_IF_NO_NODE(block); + + bool will_emit_entrypoint = false; + + if (!mEntryPointEmitted) { + will_emit_entrypoint = true; + + // We are going to emit the entry point at the end when we emit this block, but we dont want the function definitions in it to leech and steal + // our entry marker. + mEntryPointEmitted = true; + } + + bool does_block_branch = false; + + if (!ignore_function_definitions) { + // Before outputting any statements output any function definitions in the block. + EmitFunctionDefinitionsInBlock(block); + } + + // For each var declared in the block, write a stack allocation in the frame header + for (FoxAstNode* node : block->Statements) { + if (!does_block_branch && DoesNodeBranch(node)) { + does_block_branch = true; + } + + // Ignore function definitions when emitting the block statements as they are handled elsewhere! + if (node->NodeType == FX_AST_ACTIONDECL) { + continue; + } + else if (node->NodeType == FX_AST_VARDECL) { + FoxAstVarDecl* var_decl = reinterpret_cast(node); + + uint32 stack_index = mStackOffset; + + EmitStackAlloc(GetSizeOfType(var_decl->Type)); + + FoxBytecodeVarHandle var_handle { + .HashedName = var_decl->Name->GetHash(), + .Offset = stack_index, + .ScopeIndex = mScopeIndex, + + .VarIndexInScope = mVarsInScope, + .Type = FoxValue::INT, + }; + + VarHandles.Insert(var_handle); + + ++mVarsInScope; + } + } + + if (will_emit_entrypoint) { + EmitMarker(IrSpecMarker_EntryPoint); + } + + if (does_block_branch) { + EmitMarker(IrSpecMarker_FunctionBranches); + } + + + // After the stack allocations, mark the start of the frame. + EmitMarker(IrSpecMarker_FrameBegin); + + for (FoxAstNode* node : block->Statements) { + Emit(node); + } + + mVarsInScope = 0; + EmitMarker(IrSpecMarker_FrameEnd); +} + +void FoxIREmitter::PrintBytecode() +{ + const size_t size = mBytecode.Size(); + for (int i = 0; i < 25; i++) { + printf("%02d ", i); + } + printf("\n"); + for (int i = 0; i < 25; i++) { + printf("---"); + } + printf("\n"); + + for (size_t i = 0; i < size; i++) { + printf("%02X ", mBytecode[i]); + + if (i > 0 && ((i + 1) % 25) == 0) { + printf("\n"); + } + } + printf("\n"); +} + +bool FoxIREmitter::DoesNodeBranch(FoxAstNode* node) +{ + if (node == nullptr) { + return false; + } + + if (node->NodeType == FX_AST_ACTIONCALL) { + return true; + } + + else if (node->NodeType == FX_AST_BLOCK) { + FoxAstBlock* block = reinterpret_cast(node); + for (FoxAstNode* stmt : block->Statements) { + if (DoesNodeBranch(stmt)) { + return true; + } + } + + return false; + } + + else if (node->NodeType == FX_AST_VARDECL) { + FoxAstVarDecl* vardecl = reinterpret_cast(node); + + return DoesNodeBranch(vardecl->Assignment); + } + else if (node->NodeType == FX_AST_BINOP) { + FoxAstBinop* binop = reinterpret_cast(node); + return (DoesNodeBranch(binop->Left) || DoesNodeBranch(binop->Right)); + } + + else if (node->NodeType == FX_AST_ASSIGN) { + FoxAstAssign* assign = reinterpret_cast(node); + + return DoesNodeBranch(assign->Rhs); + } + + return false; +} + +#pragma endregion IrEmitter + + +///////////////////////////////////// +// Bytecode Printer +///////////////////////////////////// + +uint16 FoxIRPrinter::Read16() +{ + uint8 lo = mBytecode[mBytecodeIndex++]; + uint8 hi = mBytecode[mBytecodeIndex++]; + + return ((static_cast(lo) << 8) | hi); +} + +uint32 FoxIRPrinter::Read32() +{ + uint16 lo = Read16(); + uint16 hi = Read16(); + + return ((static_cast(lo) << 16) | hi); +} + +#define BC_PRINT_OP(fmt_, ...) snprintf(s, 128, fmt_, ##__VA_ARGS__) + +void FoxIRPrinter::DoLoad(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == IrSpecLoad_Int32) { + int16 offset = Read16(); + BC_PRINT_OP("load [i32] %d, %s", offset, FoxIREmitter::GetRegisterName(static_cast(op_reg))); + } + else if (op_spec == IrSpecLoad_AbsoluteInt32) { + uint32 offset = Read32(); + BC_PRINT_OP("loada [i32] %u, %s", offset, FoxIREmitter::GetRegisterName(static_cast(op_reg))); + } +} + +void FoxIRPrinter::DoPush(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecPush_Int32) { + uint32 value = Read32(); + BC_PRINT_OP("push [i32] %u", value); + } + else if (op_spec == IrSpecPush_Reg32) { + uint16 reg = Read16(); + BC_PRINT_OP("push [r32] %s", FoxIREmitter::GetRegisterName(static_cast(reg))); + } + else if (op_spec == IrSpecPush_StackAlloc) { + uint16 size = Read16(); + BC_PRINT_OP("salloc %d", size); + } +} + +void FoxIRPrinter::DoPop(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == IrSpecPop_Int32) { + BC_PRINT_OP("pop [i32] %s", FoxIREmitter::GetRegisterName(static_cast(op_reg))); + } +} + +void FoxIRPrinter::DoArith(char* s, uint8 op_base, uint8 op_spec) +{ + uint8 a_reg = mBytecode[mBytecodeIndex++]; + uint8 b_reg = mBytecode[mBytecodeIndex++]; + + if (op_spec == IrSpecArith_Add_Reg32) { + BC_PRINT_OP("add [i32] %s, %s", FoxIREmitter::GetRegisterName(static_cast(a_reg)), + FoxIREmitter::GetRegisterName(static_cast(b_reg))); + } +} + +void FoxIRPrinter::DoSave(char* s, uint8 op_base, uint8 op_spec) +{ + // Save a imm32 into an offset in the stack + if (op_spec == IrSpecSave_Int32) { + const int16 offset = Read16(); + const uint32 value = Read32(); + + BC_PRINT_OP("save [i32] %d, %u", offset, value); + } + + // Save a register into an offset in the stack + else if (op_spec == IrSpecSave_Reg32) { + const int16 offset = Read16(); + uint16 reg = Read16(); + + BC_PRINT_OP("save [r32] %d, %s", offset, FoxIREmitter::GetRegisterName(static_cast(reg))); + } + else if (op_spec == IrSpecSave_AbsoluteInt32) { + const uint32 offset = Read32(); + const uint32 value = Read32(); + + BC_PRINT_OP("savea [i32] %u, %u", offset, value); + } + else if (op_spec == IrSpecSave_AbsoluteReg32) { + const uint32 offset = Read32(); + uint16 reg = Read16(); + + BC_PRINT_OP("savea [r32] %u, %s", offset, FoxIREmitter::GetRegisterName(static_cast(reg))); + } +} + +void FoxIRPrinter::DoJump(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecJump_Relative) { + uint16 offset = Read16(); + printf("// jump relative to (%u)\n", mBytecodeIndex + offset); + BC_PRINT_OP("jmpr %d", offset); + } + else if (op_spec == IrSpecJump_Absolute) { + uint32 position = Read32(); + BC_PRINT_OP("jmpa %u", position); + } + else if (op_spec == IrSpecJump_AbsoluteReg32) { + uint16 reg = Read16(); + BC_PRINT_OP("jmpar %s", FoxIREmitter::GetRegisterName(static_cast(reg))); + } + else if (op_spec == IrSpecJump_CallAbsolute) { + uint32 position = Read32(); + BC_PRINT_OP("calla %u", position); + } + else if (op_spec == IrSpecJump_ReturnToCaller) { + BC_PRINT_OP("ret"); + } + else if (op_spec == IrSpecJump_ReturnToCaller_Reg32) { + const char* reg_name = FoxIREmitter::GetRegisterName(static_cast(Read16())); + + BC_PRINT_OP("ret [r32] %s", reg_name); + } + else if (op_spec == IrSpecJump_ReturnToCaller_Int32) { + int32 value = Read32(); + BC_PRINT_OP("ret [i32] %d", value); + } + else if (op_spec == IrSpecJump_CallExternal) { + uint32 hashed_name = Read32(); + BC_PRINT_OP("callext %u", hashed_name); + } +} + + +void FoxIRPrinter::DoData(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecData_String) { + uint16 length = Read16(); + char* data_str = FX_SCRIPT_ALLOC_MEMORY(char, length); + uint16* data_str16 = reinterpret_cast(data_str); + + uint32 bytecode_end = mBytecodeIndex + length; + int data_index = 0; + while (mBytecodeIndex < bytecode_end) { + uint16 value16 = Read16(); + data_str16[data_index++] = ((value16 << 8) | (value16 >> 8)); + } + + BC_PRINT_OP("datastr %d, %.*s", length, length, data_str); + + FX_SCRIPT_FREE(char, data_str); + } +} + +void FoxIRPrinter::DoType(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecType_Int) { + BC_PRINT_OP("type int"); + } + else if (op_spec == IrSpecType_String) { + BC_PRINT_OP("type str"); + } +} + +void FoxIRPrinter::DoMove(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == IrSpecMove_Int32) { + uint32 value = Read32(); + BC_PRINT_OP("move [i32] %s, %u\t", FoxIREmitter::GetRegisterName(static_cast(op_reg)), value); + } +} + +void FoxIRPrinter::DoMarker(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecMarker_FrameBegin) { + BC_PRINT_OP("@FrameBegin"); + } + else if (op_spec == IrSpecMarker_FrameEnd) { + BC_PRINT_OP("@FrameEnd"); + } + else if (op_spec == IrSpecMarker_ParamsBegin) { + BC_PRINT_OP("@Params"); + } + else if (op_spec == IrSpecMarker_EntryPoint) { + BC_PRINT_OP("@Entry"); + } + else if (op_spec == IrSpecMarker_FunctionBranches) { + BC_PRINT_OP("@Branches"); + } +} + + +void FoxIRPrinter::DoVariable(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecVariable_Get_Int32) { + uint16 var_index = Read16(); + FoxIRRegister dest_reg = static_cast(Read16()); + BC_PRINT_OP("vget [i32] $%d, %s", var_index, FoxIREmitter::GetRegisterName(dest_reg)); + } + else if (op_spec == IrSpecVariable_Set_Int32) { + uint16 var_index = Read16(); + uint32 value = Read32(); + BC_PRINT_OP("vset [i32] $%d, %d", var_index, value); + } + else if (op_spec == IrSpecVariable_Set_Reg32) { + uint16 var_index = Read16(); + FoxIRRegister reg = static_cast(Read16()); + BC_PRINT_OP("vset [r32] $%d, %s", var_index, FoxIREmitter::GetRegisterName(reg)); + } +} + + +void FoxIRPrinter::Print() +{ + while (mBytecodeIndex < mBytecode.Size()) { + PrintOp(); + } +} + +void FoxIRPrinter::PrintOp() +{ + uint32 bc_index = mBytecodeIndex; + + uint16 op_full = Read16(); + + const uint8 op_base = static_cast(op_full >> 8); + const uint8 op_spec = static_cast(op_full & 0xFF); + + char s[128]; + + switch (op_base) { + case IrBase_Push: + DoPush(s, op_base, op_spec); + break; + case IrBase_Pop: + DoPop(s, op_base, op_spec); + break; + case IrBase_Load: + DoLoad(s, op_base, op_spec); + break; + case IrBase_Arith: + DoArith(s, op_base, op_spec); + break; + case IrBase_Jump: + DoJump(s, op_base, op_spec); + break; + case IrBase_Save: + DoSave(s, op_base, op_spec); + break; + case IrBase_Data: + DoData(s, op_base, op_spec); + break; + case IrBase_Type: + DoType(s, op_base, op_spec); + break; + case IrBase_Move: + DoMove(s, op_base, op_spec); + break; + case IrBase_Marker: + DoMarker(s, op_base, op_spec); + break; + case IrBase_Variable: + DoVariable(s, op_base, op_spec); + break; + } + + printf("%-25s", s); + + printf("\t// Offset: %u\n", bc_index); +} + + +///////////////////////////////////// +// FxIRToArm64 +///////////////////////////////////// + +uint16 FoxIRToArm64::Read16() +{ + uint8 lo = mBytecode[mBytecodeIndex++]; + uint8 hi = mBytecode[mBytecodeIndex++]; + + return ((static_cast(lo) << 8) | hi); +} + +uint32 FoxIRToArm64::Read32() +{ + uint16 lo = Read16(); + uint16 hi = Read16(); + + return ((static_cast(lo) << 16) | hi); +} + +#define BC_PRINT_OP(fmt_, ...) snprintf(s, 128, fmt_, ##__VA_ARGS__) + +void FoxIRToArm64::DoLoad(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == IrSpecLoad_Int32) { + int16 offset = Read16(); + BC_PRINT_OP("load [i32] %d, %s", offset, FoxIREmitter::GetRegisterName(static_cast(op_reg))); + } + else if (op_spec == IrSpecLoad_AbsoluteInt32) { + uint32 offset = Read32(); + BC_PRINT_OP("loada [i32] %u, %s", offset, FoxIREmitter::GetRegisterName(static_cast(op_reg))); + } +} + +void FoxIRToArm64::DoPush(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecPush_Int32) { + uint32 value = Read32(); + BC_PRINT_OP("push [i32] %u", value); + } + else if (op_spec == IrSpecPush_Reg32) { + uint16 reg = Read16(); + BC_PRINT_OP("push [r32] %s", FoxIREmitter::GetRegisterName(static_cast(reg))); + } + else if (op_spec == IrSpecPush_StackAlloc) { + uint16 size = Read16(); + + PreFrameStackAllocation += size; + + BC_PRINT_OP("// salloc %d", size); + } +} + +void FoxIRToArm64::DoPop(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == IrSpecPop_Int32) { + BC_PRINT_OP("pop [i32] %s", FoxIREmitter::GetRegisterName(static_cast(op_reg))); + } +} + +void FoxIRToArm64::DoArith(char* s, uint8 op_base, uint8 op_spec) +{ + FoxIRRegister a_reg = static_cast(mBytecode[mBytecodeIndex++]); + FoxIRRegister b_reg = static_cast(mBytecode[mBytecodeIndex++]); + + if (op_spec == IrSpecArith_Add_Reg32) { + // BC_PRINT_OP("add [i32] %s, %s", FoxIREmitter::GetRegisterName(static_cast(a_reg)), + // FoxIREmitter::GetRegisterName(static_cast(b_reg))); + + const char* lhs_reg = GetRegisterName(GetArmRegFromIRReg(a_reg)); + const char* rhs_reg = GetRegisterName(GetArmRegFromIRReg(b_reg)); + + BC_PRINT_OP("add %s, %s, %s", lhs_reg, lhs_reg, rhs_reg); + } +} + +void FoxIRToArm64::DoSave(char* s, uint8 op_base, uint8 op_spec) +{ + // Save a imm32 into an offset in the stack + if (op_spec == IrSpecSave_Int32) { + const int16 offset = Read16(); + const uint32 value = Read32(); + + BC_PRINT_OP("save [i32] %d, %u", offset, value); + } + + // Save a register into an offset in the stack + else if (op_spec == IrSpecSave_Reg32) { + const int16 offset = Read16(); + uint16 reg = Read16(); + + BC_PRINT_OP("save [r32] %d, %s", offset, FoxIREmitter::GetRegisterName(static_cast(reg))); + } + else if (op_spec == IrSpecSave_AbsoluteInt32) { + const uint32 offset = Read32(); + const uint32 value = Read32(); + + BC_PRINT_OP("savea [i32] %u, %u", offset, value); + } + else if (op_spec == IrSpecSave_AbsoluteReg32) { + const uint32 offset = Read32(); + uint16 reg = Read16(); + + BC_PRINT_OP("savea [r32] %u, %s", offset, FoxIREmitter::GetRegisterName(static_cast(reg))); + } +} + +void FoxIRToArm64::EmitFrameRestore() +{ + FoxIRArm64Frame* current_frame = GetCurrentFrame(); + + if (mFunctionContainsBranches) { + printf("ldp x29, x30, [sp, #%u]\n", GetCurrentFrame()->StackAllocated); + } + + if (current_frame->StackAllocated != 0 || mFunctionContainsBranches) { + int total_allocated = current_frame->StackAllocated; + + // If we do branch, we have saved the FP and LR registers to the stack. Each one is 8 bytes long (uint64), so we will need to keep that in + // mind when restoring the stack frame. + if (mFunctionContainsBranches) { + total_allocated += sizeof(uint64) * 2; + } + + printf("add sp, sp, #%u\n", total_allocated); + } + + mFunctionContainsBranches = false; +} + +void FoxIRToArm64::DoJump(char* s, uint8 op_base, uint8 op_spec) +{ + FoxIRArm64Frame* current_frame = GetCurrentFrame(); + + if (op_spec == IrSpecJump_Relative) { + uint16 offset = Read16(); + printf("// jump relative to (%u)\n", mBytecodeIndex + offset); + BC_PRINT_OP("jmpr %d", offset); + } + else if (op_spec == IrSpecJump_Absolute) { + uint32 position = Read32(); + BC_PRINT_OP("jmpa %u", position); + } + else if (op_spec == IrSpecJump_AbsoluteReg32) { + uint16 reg = Read16(); + BC_PRINT_OP("jmpar %s", FoxIREmitter::GetRegisterName(static_cast(reg))); + } + else if (op_spec == IrSpecJump_CallAbsolute) { + uint32 position = Read32(); + BC_PRINT_OP("bl _R_%u", position); + } + else if (op_spec == IrSpecJump_CallExternal) { + uint32 hashed_name = Read32(); + + BC_PRINT_OP("callext %u", hashed_name); + } + + else if (op_spec == IrSpecJump_ReturnToCaller) { + current_frame->HasBaselevelReturnStmt = true; + EmitFrameRestore(); + BC_PRINT_OP("ret"); + } + else if (op_spec == IrSpecJump_ReturnToCaller_Reg32) { + current_frame->HasBaselevelReturnStmt = true; + + const FoxArm64Register value_reg = GetArmRegFromIRReg(static_cast(Read16())); + + if (value_reg != Fox_Arm64_W0) { + printf("mov w0, %s\n", GetRegisterName(value_reg)); + } + + EmitFrameRestore(); + BC_PRINT_OP("ret"); + } + else if (op_spec == IrSpecJump_ReturnToCaller_Int32) { + current_frame->HasBaselevelReturnStmt = true; + + printf("mov w0, #%d\n", Read32()); + EmitFrameRestore(); + BC_PRINT_OP("ret"); + } +} + + +void FoxIRToArm64::DoData(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecData_String) { + uint16 length = Read16(); + char* data_str = FX_SCRIPT_ALLOC_MEMORY(char, length); + uint16* data_str16 = reinterpret_cast(data_str); + + uint32 bytecode_end = mBytecodeIndex + length; + int data_index = 0; + while (mBytecodeIndex < bytecode_end) { + uint16 value16 = Read16(); + data_str16[data_index++] = ((value16 << 8) | (value16 >> 8)); + } + + BC_PRINT_OP("// datastr %d, %.*s", length, length, data_str); + + FX_SCRIPT_FREE(char, data_str); + } +} + +void FoxIRToArm64::DoType(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecType_Int) { + BC_PRINT_OP("// type int"); + } + else if (op_spec == IrSpecType_String) { + BC_PRINT_OP("// type str"); + } +} + +void FoxIRToArm64::DoMove(char* s, uint8 op_base, uint8 op_spec_raw) +{ + uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); + uint8 op_reg = (op_spec_raw & 0x0F); + + if (op_spec == IrSpecMove_Int32) { + int value = static_cast(Read32()); + const FoxArm64Register dest_reg = GetArmRegFromIRReg(static_cast(op_reg)); + + // BC_PRINT_OP("move [i32] %s, %u\t", FoxIREmitter::GetRegisterName(), value); + + BC_PRINT_OP("mov %s, #%d", GetRegisterName(dest_reg), value); + } +} + +void FoxIRToArm64::DoMarker(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecMarker_FrameBegin) { + uint32 stack_allocation = MakeValueFactorOf16(PreFrameStackAllocation); + + // Storage for the frame pointer and link register (x29 & x30) + + FoxIRArm64Frame* current_frame = FramePush(); + current_frame->StackAllocated = stack_allocation; + + constexpr uint32 size_of_opcode = sizeof(uint16); + + // The function definition index is the current bytecode index minus the size of an opcode, + // as the current opcode is loaded into memory(thus offsetting the bytecode index) + const uint32 function_bytecode_index = mBytecodeIndex - size_of_opcode; + + if (mEmitDefinitionAsEntryPoint) { + printf("_main:\n"); + } + else { + printf("_R_%d:\n", function_bytecode_index); + } + + // If there are no stack allocations and the current frame does not branch, do not create a new stack frame. + if (current_frame->StackAllocated != 0 || mFunctionContainsBranches) { + int total_allocated = current_frame->StackAllocated; + + // If we do branch, we need to save the FP and LR registers to the stack. Each one is 8 bytes long (uint64). + if (mFunctionContainsBranches) { + total_allocated += sizeof(uint64) * 2; + } + + // Allocate the stack frame + printf("sub sp, sp, #%u\n", total_allocated); + } + + if (mFunctionContainsBranches) { + printf("stp x29, x30, [sp, #%u]\n", current_frame->StackAllocated); + // Move the FP back to ignore the storage for the above + BC_PRINT_OP("add x29, sp, #16"); + } + else { + BC_PRINT_OP(""); + } + } + else if (op_spec == IrSpecMarker_FrameEnd) { + // If there is a return statement on base level (without branching, conditions, etc.) then we can + // omit the frame restore logic here as it will already be covered by the return statement. + if (!GetCurrentFrame()->HasBaselevelReturnStmt) { + EmitFrameRestore(); + } + + BC_PRINT_OP("// End of frame"); + FramePop(); + + // Reset the current stack frame + } + else if (op_spec == IrSpecMarker_ParamsBegin) { + BC_PRINT_OP("// params begin"); + } + else if (op_spec == IrSpecMarker_EntryPoint) { + mEmitDefinitionAsEntryPoint = true; + BC_PRINT_OP("// Entry point"); + } + else if (op_spec == IrSpecMarker_FunctionBranches) { + mFunctionContainsBranches = true; + + BC_PRINT_OP("// Branches"); + } +} + + +void FoxIRToArm64::DoVariable(char* s, uint8 op_base, uint8 op_spec) +{ + if (op_spec == IrSpecVariable_Get_Int32) { + uint16 var_index = Read16(); + // BC_PRINT_OP("vget [i32] $%d, %s", var_index, FoxIREmitter::GetRegisterName(dest_reg)); + + const FoxIRRegister dest_ir_reg = static_cast(Read16()); + const FoxArm64Register dest_reg = GetArmRegFromIRReg(dest_ir_reg); + + const uint32 var_stack_offset = GetCurrentFrame()->StackAllocated - ((var_index + 1) * 4); + + BC_PRINT_OP("ldr %s, [sp, #%u]", GetRegisterName(dest_reg), var_stack_offset); + } + else if (op_spec == IrSpecVariable_Set_Int32) { + uint16 var_index = Read16(); + uint32 value = Read32(); + // BC_PRINT_OP("vset [i32] $%d, %d", var_index, value); + + FoxArm64Register reg = RegisterRequest(Usage_General); + + const char* reg_name = GetRegisterName(reg); + + printf("mov %s, #%d\n", reg_name, static_cast(value)); + + const uint32 var_stack_offset = GetCurrentFrame()->StackAllocated - ((var_index + 1) * 4); + + BC_PRINT_OP("str %s, [sp, #%u]", reg_name, var_stack_offset); + + RegisterRelease(reg); + } + else if (op_spec == IrSpecVariable_Set_Reg32) { + uint16 var_index = Read16(); + FoxIRRegister reg = static_cast(Read16()); + + const uint32 var_stack_offset = GetCurrentFrame()->StackAllocated - ((var_index + 1) * 4); + + BC_PRINT_OP("str %s, [sp, #%u]", GetRegisterName(GetArmRegFromIRReg(reg)), var_stack_offset); + // BC_PRINT_OP("vset [r32] $%d, %s", var_index, FoxIREmitter::GetRegisterName(reg)); + } +} + + +void FoxIRToArm64::Print() +{ + printf(".global _main\n\n"); + while (mBytecodeIndex < mBytecode.Size()) { + PrintOp(); + } +} + +void FoxIRToArm64::PrintOp() +{ + uint32 bc_index = mBytecodeIndex; + + uint16 op_full = Read16(); + + const uint8 op_base = static_cast(op_full >> 8); + const uint8 op_spec = static_cast(op_full & 0xFF); + + char s[128]; + + switch (op_base) { + case IrBase_Push: + DoPush(s, op_base, op_spec); + break; + case IrBase_Pop: + DoPop(s, op_base, op_spec); + break; + case IrBase_Load: + DoLoad(s, op_base, op_spec); + break; + case IrBase_Arith: + DoArith(s, op_base, op_spec); + break; + case IrBase_Jump: + DoJump(s, op_base, op_spec); + break; + case IrBase_Save: + DoSave(s, op_base, op_spec); + break; + case IrBase_Data: + DoData(s, op_base, op_spec); + break; + case IrBase_Type: + DoType(s, op_base, op_spec); + break; + case IrBase_Move: + DoMove(s, op_base, op_spec); + break; + case IrBase_Marker: + DoMarker(s, op_base, op_spec); + break; + case IrBase_Variable: + DoVariable(s, op_base, op_spec); + break; + } + + printf("%-25s", s); + + printf("\t// Offset: %u\n", bc_index); +} + +uint32 FoxIRToArm64::MakeValueFactorOf16(uint32 value) +{ + // Get the bottom four bits (value % 16) + const uint8 remainder = (value & 0x0F); + + if (remainder != 0) { + value += (16 - remainder); + } + + return value; +} + +FoxArm64Register FoxIRToArm64::RegisterRequest(FoxIRToArm64::RegisterUsage usage) +{ + FoxArm64Register min, max; + + switch (usage) { + case Usage_Parameters: + min = Fox_Arm64_W0; + max = Fox_Arm64_W7; + break; + case Usage_General: + min = Fox_Arm64_W8; + max = Fox_Arm64_W15; + break; + case Usage_Return: + RegisterRelease(Fox_Arm64_W0); + + min = Fox_Arm64_W0; + max = Fox_Arm64_W0; + break; + } + + while (min <= max) { + const uint32 reg_flag = (1 << min); + + FoxIRArm64Frame* current_frame = &mStackFrames.GetLast(); + + // If register is not in use, return it + if (!(current_frame->RegistersInUse & reg_flag)) { + // Mark the register as in use + current_frame->RegistersInUse |= reg_flag; + + return min; + } + + // Increment the register + min = static_cast(static_cast(min) + 1); + } + + + return Fox_Arm64_None; +} + + +void FoxIRToArm64::RegisterRelease(FoxArm64Register reg) +{ + GetCurrentFrame()->RegistersInUse &= ~(1 << reg); +} + +bool FoxIRToArm64::IsRegisterInUse(FoxArm64Register reg) +{ + return (GetCurrentFrame()->RegistersInUse & (1 << static_cast(reg))); +} + + +FoxArm64Register FoxIRToArm64::GetArmRegFromIRReg(FoxIRRegister ir_reg) +{ + switch (ir_reg) { + case FX_IR_REG_RETURN_VALUE: + return Fox_Arm64_W0; + default: + break; + } + + return static_cast(static_cast(Fox_Arm64_W8) + static_cast(ir_reg)); +} + +FoxIRArm64Frame* FoxIRToArm64::GetCurrentFrame() +{ + if (mStackFrames.IsEmpty()) { + return nullptr; + } + + return &mStackFrames.GetLast(); +} + +FoxIRArm64Frame* FoxIRToArm64::FramePush() +{ + if (!mStackFrames.IsInited()) { + mStackFrames.Create(16); + } + + PreFrameStackAllocation = 0; + + return mStackFrames.Insert(); +} + +void FoxIRToArm64::FramePop() +{ + mStackFrames.RemoveLast(); +} + + +const char* FoxIRToArm64::GetRegisterName(FoxArm64Register reg) +{ + switch (reg) { + case Fox_Arm64_W0: + return "w0"; + case Fox_Arm64_W1: + return "w1"; + case Fox_Arm64_W2: + return "w2"; + case Fox_Arm64_W3: + return "w3"; + case Fox_Arm64_W4: + return "w4"; + case Fox_Arm64_W5: + return "w5"; + case Fox_Arm64_W6: + return "w6"; + case Fox_Arm64_W7: + return "w7"; + case Fox_Arm64_W8: + return "w8"; + case Fox_Arm64_W9: + return "w9"; + case Fox_Arm64_W10: + return "w10"; + case Fox_Arm64_W11: + return "w11"; + case Fox_Arm64_W12: + return "w12"; + case Fox_Arm64_W13: + return "w13"; + case Fox_Arm64_W14: + return "w14"; + case Fox_Arm64_W15: + return "w15"; + case Fox_Arm64_None: + return "(none)"; + } + + return "(unknown)"; +} diff --git a/FoxScript.hpp b/FoxScript.hpp new file mode 100644 index 0000000..5cf5b6a --- /dev/null +++ b/FoxScript.hpp @@ -0,0 +1,1333 @@ +#pragma once + +#include "FoxMPPagedArray.hpp" +#include "FoxTokenizer.hpp" + +#include + +#define FX_SCRIPT_VERSION_MAJOR 0 +#define FX_SCRIPT_VERSION_MINOR 3 +#define FX_SCRIPT_VERSION_PATCH 2 + +#define FX_SCRIPT_VAR_RETURN_VAL "__ReturnVal__" + + +struct FoxAstVarRef; +struct FoxScope; +struct FoxFunction; + +struct FoxValue +{ + static FoxValue None; + + enum ValueType : uint16 + { + NONETYPE = 0x00, + INT = 0x01, + FLOAT = 0x02, + STRING = 0x04, + VEC3 = 0x08, + REF = 0x10 + }; + + ValueType Type = NONETYPE; + + union + { + int ValueInt = 0; + float ValueFloat; + float ValueVec3[3]; + char* ValueString; + + FoxAstVarRef* ValueRef; + }; + + FoxValue() + { + } + + explicit FoxValue(ValueType type, int value) : Type(type), ValueInt(value) + { + } + + explicit FoxValue(ValueType type, float value) : Type(type), ValueFloat(value) + { + } + + FoxValue(const FoxValue& other) + { + Type = other.Type; + if (other.Type == INT) { + ValueInt = other.ValueInt; + } + else if (other.Type == FLOAT) { + ValueFloat = other.ValueFloat; + } + else if (other.Type == STRING) { + ValueString = other.ValueString; + } + else if (other.Type == REF) { + ValueRef = other.ValueRef; + } + } + + void Print() const + { + printf("[Value: "); + if (Type == NONETYPE) { + printf("Null]\n"); + } + else if (Type == INT) { + printf("Int, %d]\n", ValueInt); + } + else if (Type == FLOAT) { + printf("Float, %f]\n", ValueFloat); + } + else if (Type == STRING) { + printf("String, %s]\n", ValueString); + } + else if (Type == REF) { + printf("Ref, %p]\n", ValueRef); + } + } + + inline bool IsNumber() + { + return (Type == INT || Type == FLOAT); + } + + inline bool IsRef() + { + return (Type == REF); + } +}; + +enum FoxAstType +{ + FX_AST_LITERAL, + // FX_AST_NAME, + + FX_AST_BINOP, + FX_AST_UNARYOP, + FX_AST_BLOCK, + + // Variables + FX_AST_VARREF, + FX_AST_VARDECL, + FX_AST_ASSIGN, + + // Functions + FX_AST_ACTIONDECL, + FX_AST_ACTIONCALL, + FX_AST_RETURN, + + FX_AST_DOCCOMMENT, + + FX_AST_COMMANDMODE, +}; + +struct FoxAstNode +{ + FoxAstType NodeType; +}; + + +struct FoxAstLiteral : public FoxAstNode +{ + FoxAstLiteral() + { + this->NodeType = FX_AST_LITERAL; + } + + // FoxTokenizer::Token* Token = nullptr; + FoxValue Value; +}; + +struct FoxAstBinop : public FoxAstNode +{ + FoxAstBinop() + { + this->NodeType = FX_AST_BINOP; + } + + FoxTokenizer::Token* OpToken = nullptr; + FoxAstNode* Left = nullptr; + FoxAstNode* Right = nullptr; +}; + +struct FoxAstBlock : public FoxAstNode +{ + FoxAstBlock() + { + this->NodeType = FX_AST_BLOCK; + } + + std::vector Statements; +}; + +struct FoxAstVarRef : public FoxAstNode +{ + FoxAstVarRef() + { + this->NodeType = FX_AST_VARREF; + } + + FoxTokenizer::Token* Name = nullptr; + FoxScope* Scope = nullptr; +}; + +struct FoxAstAssign : public FoxAstNode +{ + FoxAstAssign() + { + this->NodeType = FX_AST_ASSIGN; + } + + FoxAstVarRef* Var = nullptr; + // FoxValue Value; + FoxAstNode* Rhs = nullptr; +}; + +struct FoxAstVarDecl : public FoxAstNode +{ + FoxAstVarDecl() + { + this->NodeType = FX_AST_VARDECL; + } + + FoxTokenizer::Token* Name = nullptr; + FoxTokenizer::Token* Type = nullptr; + FoxAstAssign* Assignment = nullptr; + + /// Ignore the scope that the variable is declared in, force it to be global. + bool DefineAsGlobal = false; +}; + +struct FoxAstDocComment : public FoxAstNode +{ + FoxAstDocComment() + { + this->NodeType = FX_AST_DOCCOMMENT; + } + + FoxTokenizer::Token* Comment; +}; + +struct FoxAstFunctionDecl : public FoxAstNode +{ + FoxAstFunctionDecl() + { + this->NodeType = FX_AST_ACTIONDECL; + } + + FoxTokenizer::Token* Name = nullptr; + FoxAstVarDecl* ReturnVar = nullptr; + FoxAstBlock* Params = nullptr; + FoxAstBlock* Block = nullptr; + + std::vector DocComments; +}; + +struct FoxAstCommandMode : public FoxAstNode +{ + FoxAstCommandMode() + { + this->NodeType = FX_AST_COMMANDMODE; + } + + FoxAstNode* Node = nullptr; +}; + +struct FoxAstFunctionCall : public FoxAstNode +{ + FoxAstFunctionCall() + { + this->NodeType = FX_AST_ACTIONCALL; + } + + FoxFunction* Function = nullptr; + FoxHash HashedName = 0; + std::vector Params {}; // FoxAstLiteral or FoxAstVarRef +}; + +struct FoxAstReturn : public FoxAstNode +{ + FoxAstReturn() + { + this->NodeType = FX_AST_RETURN; + } + + FoxAstNode* Rhs = nullptr; +}; + +/** + * @brief Data is accessible from a label, such as a variable or an function. + */ +struct FoxLabelledData +{ + FoxHash HashedName = 0; + FoxTokenizer::Token* Name = nullptr; + + FoxScope* Scope = nullptr; +}; + +struct FoxFunction : public FoxLabelledData +{ + FoxFunction(FoxTokenizer::Token* name, FoxScope* scope, FoxAstBlock* block, FoxAstFunctionDecl* declaration) + { + HashedName = name->GetHash(); + Name = name; + Scope = scope; + Block = block; + Declaration = declaration; + } + + FoxAstFunctionDecl* Declaration = nullptr; + FoxAstBlock* Block = nullptr; +}; + +struct FoxVar : public FoxLabelledData +{ + FoxTokenizer::Token* Type = nullptr; + FoxValue Value; + + bool IsExternal = false; + + void Print() const + { + printf("[Var] Type: %.*s, Name: %.*s (Hash:%u)", Type->Length, Type->Start, Name->Length, Name->Start, Name->GetHash()); + Value.Print(); + } + + FoxVar() + { + } + + FoxVar(FoxTokenizer::Token* type, FoxTokenizer::Token* name, FoxScope* scope, bool is_external = false) : Type(type) + { + this->HashedName = name->GetHash(); + this->Name = name; + this->Scope = scope; + IsExternal = is_external; + } + + FoxVar(const FoxVar& other) + { + HashedName = other.HashedName; + Type = other.Type; + Name = other.Name; + Value = other.Value; + IsExternal = other.IsExternal; + } + + FoxVar& operator=(FoxVar&& other) noexcept + { + HashedName = other.HashedName; + Type = other.Type; + Name = other.Name; + Value = other.Value; + IsExternal = other.IsExternal; + + Name = nullptr; + Type = nullptr; + HashedName = 0; + + return *this; + } + + ~FoxVar() + { + if (!IsExternal) { + return; + } + + // Free tokens allocated by external variables + if (Type && Type->Start) { + FX_SCRIPT_FREE(char, Type->Start); + } + + if (this->Name && this->Name->Start) { + FX_SCRIPT_FREE(char, this->Name->Start); + } + } +}; + +class FoxVM; + + +struct FoxExternalFunc +{ + // using FuncType = void (*)(FoxInterpreter& interpreter, std::vector& params, FoxValue* return_value); + + using FuncType = void (*)(FoxVM* vm, std::vector& params, FoxValue* return_value); + + FoxHash HashedName = 0; + FuncType Function = nullptr; + + std::vector ParameterTypes; + bool IsVariadic = false; +}; + +struct FoxScope +{ + FoxMPPagedArray Vars; + FoxMPPagedArray Functions; + + FoxScope* Parent = nullptr; + + // This points to the return value for the current scope. If an function returns a value, + // this will be set to the variable that holds its value. This is interpreter only. + FoxVar* ReturnVar = nullptr; + + void PrintAllVarsInScope() + { + puts("\n=== SCOPE ==="); + for (FoxVar& var : Vars) { + var.Print(); + } + } + + FoxVar* FindVarInScope(FoxHash hashed_name) + { + return FindInScope(hashed_name, Vars); + } + + FoxFunction* FindFunctionInScope(FoxHash hashed_name) + { + return FindInScope(hashed_name, Functions); + } + + template + requires std::is_base_of_v + T* FindInScope(FoxHash hashed_name, const FoxMPPagedArray& buffer) + { + for (T& var : buffer) { + if (var.HashedName == hashed_name) { + return &var; + } + } + + return nullptr; + } +}; + +// class FoxInterpreter; + +class FoxConfigScript +{ + using Token = FoxTokenizer::Token; + using TT = FoxTokenizer::TokenType; + +public: + FoxConfigScript() = default; + + void LoadFile(const char* path); + + void PushScope(); + void PopScope(); + + FoxVar* FindVar(FoxHash hashed_name); + + FoxFunction* FindFunction(FoxHash hashed_name); + FoxExternalFunc* FindExternalFunction(FoxHash hashed_name); + + FoxAstNode* TryParseKeyword(FoxAstBlock* parent_block); + + FoxAstAssign* TryParseAssignment(FoxTokenizer::Token* var_name); + + FoxValue ParseValue(); + + FoxAstFunctionDecl* ParseFunctionDeclare(); + + FoxAstNode* ParseRhs(); + FoxAstFunctionCall* ParseFunctionCall(); + + /** + * @brief Declares a variable for internal uses as if it was declared in the script. + * @param name Name of the variable + * @param type The name of the type + * @param scope The scope the variable will be declared in + * @return + */ + FoxAstVarDecl* InternalVarDeclare(FoxTokenizer::Token* name_token, FoxTokenizer::Token* type_token, FoxScope* scope = nullptr); + FoxAstVarDecl* ParseVarDeclare(FoxScope* scope = nullptr); + + FoxAstBlock* ParseBlock(); + + FoxAstNode* ParseStatement(FoxAstBlock* parent_block); + FoxAstNode* ParseStatementAsCommand(FoxAstBlock* parent_block); + + FoxAstBlock* Parse(); + + /** + * @brief Parses and executes a script. + * @param interpreter The interpreter to execute with + */ + void Execute(FoxVM& vm); + + /** + * @brief Executes a command on a script. Defaults to parsing with command style syntax. + * @param command The command to execute on the script. + * @return If the command has been executed + */ + // bool ExecuteUserCommand(const char* command, FoxInterpreter& interpreter); + + Token& GetToken(int offset = 0); + Token& EatToken(TT token_type); + + void RegisterExternalFunc(FoxHash func_name, std::vector param_types, FoxExternalFunc::FuncType func, bool is_variadic); + + void DefineExternalVar(const char* type, const char* name, const FoxValue& value); + +private: + template + requires std::is_base_of_v + T* FindLabelledData(FoxHash hashed_name, FoxMPPagedArray& buffer) + { + FoxScope* scope = mCurrentScope; + + while (scope) { + T* var = scope->FindInScope(hashed_name, buffer); + if (var) { + return var; + } + + scope = scope->Parent; + } + + return nullptr; + } + + void DefineDefaultExternalFunctions(); + + Token* CreateTokenFromString(FoxTokenizer::TokenType type, const char* text); + void CreateInternalVariableTokens(); + +private: + FoxMPPagedArray mScopes; + FoxScope* mCurrentScope; + + std::vector mExternalFuncs; + + std::vector CurrentDocComments; + + FoxAstBlock* mRootBlock = nullptr; + + bool mHasErrors = false; + bool mInCommandMode = false; + + char* mFileData; + FoxMPPagedArray mTokens = {}; + uint32 mTokenIndex = 0; + + // Name tokens for internal variables + Token* mTokenReturnVar = nullptr; +}; + +////////////////////////////////// +// Script AST Printer +////////////////////////////////// + +class FoxAstPrinter +{ +public: + FoxAstPrinter(FoxAstBlock* root_block) + //: mRootBlock(root_block) + { + } + + void Print(FoxAstNode* node, int depth = 0) + { + if (node == nullptr) { + return; + } + + for (int i = 0; i < depth; i++) { + putchar(' '); + putchar(' '); + } + + if (node->NodeType == FX_AST_BLOCK) { + puts("[BLOCK]"); + + FoxAstBlock* block = reinterpret_cast(node); + for (FoxAstNode* child : block->Statements) { + Print(child, depth + 1); + } + return; + } + else if (node->NodeType == FX_AST_ACTIONDECL) { + FoxAstFunctionDecl* functiondecl = reinterpret_cast(node); + printf("[ACTIONDECL] "); + functiondecl->Name->Print(); + + for (FoxAstNode* param : functiondecl->Params->Statements) { + Print(param, depth + 1); + } + + Print(functiondecl->Block, depth + 1); + } + else if (node->NodeType == FX_AST_VARDECL) { + FoxAstVarDecl* vardecl = reinterpret_cast(node); + + printf("[VARDECL] "); + vardecl->Name->Print(); + + Print(vardecl->Assignment, depth + 1); + } + else if (node->NodeType == FX_AST_ASSIGN) { + FoxAstAssign* assign = reinterpret_cast(node); + + printf("[ASSIGN] "); + + assign->Var->Name->Print(); + Print(assign->Rhs, depth + 1); + } + else if (node->NodeType == FX_AST_ACTIONCALL) { + FoxAstFunctionCall* functioncall = reinterpret_cast(node); + + printf("[ACTIONCALL] "); + if (functioncall->Function == nullptr) { + printf("{defined externally}"); + } + else { + functioncall->Function->Name->Print(true); + } + + printf(" (%zu params)\n", functioncall->Params.size()); + } + else if (node->NodeType == FX_AST_LITERAL) { + FoxAstLiteral* literal = reinterpret_cast(node); + + printf("[LITERAL] "); + literal->Value.Print(); + } + else if (node->NodeType == FX_AST_BINOP) { + FoxAstBinop* binop = reinterpret_cast(node); + + printf("[BINOP] "); + binop->OpToken->Print(); + + Print(binop->Left, depth + 1); + Print(binop->Right, depth + 1); + } + else if (node->NodeType == FX_AST_COMMANDMODE) { + FoxAstCommandMode* command_mode = reinterpret_cast(node); + printf("[COMMANDMODE]\n"); + + Print(command_mode->Node, depth + 1); + } + else if (node->NodeType == FX_AST_RETURN) { + FoxAstReturn* return_node = reinterpret_cast(node); + + puts("[RETURN]"); + + if (return_node->Rhs) { + Print(return_node->Rhs, depth + 1); + } + } + else { + puts("[UNKNOWN]"); + } + // else if (node->NodeType == FX_AST_) + } + +public: + // FoxAstBlock* mRootBlock = nullptr; +}; + +///////////////////////////////////////////// +// Script Bytecode Emitter +///////////////////////////////////////////// + +enum FoxBCRegister : uint8 +{ + FX_REG_NONE = 0x00, + FX_REG_X0, + FX_REG_X1, + FX_REG_X2, + FX_REG_X3, + + /** + * @brief Return address register. + */ + FX_REG_RA, + + /** + * @brief Register that contains the result of an operation. + */ + FX_REG_XR, + + /** + * @brief Register that contains the stack pointer for the VM. + */ + FX_REG_SP, + + FX_REG_SIZE, +}; + + +enum FoxRegisterFlag : uint16 +{ + FX_REGFLAG_NONE = 0x00, + FX_REGFLAG_X0 = 0x01, + FX_REGFLAG_X1 = 0x02, + FX_REGFLAG_X2 = 0x04, + FX_REGFLAG_X3 = 0x08, + FX_REGFLAG_RA = 0x10, + FX_REGFLAG_XR = 0x20, +}; + + +enum FoxIRRegister : uint8 +{ + /* General Purpose (32 bit) registers */ + FX_IR_GW0, + FX_IR_GW1, + FX_IR_GW2, + FX_IR_GW3, + + /* General Purpose (64 bit) registers */ + FX_IR_GX0, + FX_IR_GX1, + FX_IR_GX2, + FX_IR_GX3, + + FX_IR_REG_RETURN_VALUE, + + /* Stack pointer */ + FX_IR_SP, +}; + +inline FoxRegisterFlag operator|(FoxRegisterFlag a, FoxRegisterFlag b) +{ + return static_cast(static_cast(a) | static_cast(b)); +} + +inline FoxRegisterFlag operator&(FoxRegisterFlag a, FoxRegisterFlag b) +{ + return static_cast(static_cast(a) & static_cast(b)); +} + +struct FoxBytecodeVarHandle +{ + FoxHash HashedName = 0; + FoxValue::ValueType Type = FoxValue::INT; + int64 Offset = 0; + + uint16 VarIndexInScope = 0; + + uint16 SizeOnStack = 4; + uint32 ScopeIndex = 0; +}; + +struct FoxBytecodeFunctionHandle +{ + FoxHash HashedName = 0; + uint32 BytecodeIndex = 0; +}; + +class FoxBCEmitter +{ +public: + FoxBCEmitter() = default; + + void BeginEmitting(FoxAstNode* node); + void Emit(FoxAstNode* node); + + enum RhsMode + { + RHS_FETCH_TO_REGISTER, + + /** + * @brief Pushes the value to the stack, assuming that the value does not exist yet. + */ + RHS_DEFINE_IN_MEMORY, + + RHS_ASSIGN_TO_HANDLE, + }; + + static FoxBCRegister RegFlagToReg(FoxRegisterFlag reg_flag); + static FoxRegisterFlag RegToRegFlag(FoxBCRegister reg); + + static const char* GetRegisterName(FoxBCRegister reg); + + FoxMPPagedArray mBytecode {}; + + enum VarDeclareMode + { + DECLARE_DEFAULT, + DECLARE_NO_EMIT, + }; + + +private: + void EmitBlock(FoxAstBlock* block); + void EmitFunction(FoxAstFunctionDecl* function); + void DoFunctionCall(FoxAstFunctionCall* call); + FoxBytecodeVarHandle* DoVarDeclare(FoxAstVarDecl* decl, VarDeclareMode mode = DECLARE_DEFAULT); + void EmitAssign(FoxAstAssign* assign); + FoxBytecodeVarHandle* DefineAndFetchParam(FoxAstNode* param_decl_node); + FoxBytecodeVarHandle* DefineReturnVar(FoxAstVarDecl* decl); + + FoxBCRegister EmitVarFetch(FoxAstVarRef* ref, RhsMode mode); + + void DoLoad(uint32 stack_offset, FoxBCRegister output_reg, bool force_absolute = false); + void DoSaveInt32(uint32 stack_offset, uint32 value, bool force_absolute = false); + void DoSaveReg32(uint32 stack_offset, FoxBCRegister reg, bool force_absolute = false); + + void EmitPush32(uint32 value); + void EmitPush32r(FoxBCRegister reg); + + void EmitPop32(FoxBCRegister output_reg); + + void EmitLoad32(int offset, FoxBCRegister output_reg); + void EmitLoadAbsolute32(uint32 position, FoxBCRegister output_reg); + + void EmitSave32(int16 offset, uint32 value); + void EmitSaveReg32(int16 offset, FoxBCRegister reg); + + void EmitSaveAbsolute32(uint32 offset, uint32 value); + void EmitSaveAbsoluteReg32(uint32 offset, FoxBCRegister reg); + + void EmitJumpRelative(uint16 offset); + void EmitJumpAbsolute(uint32 position); + void EmitJumpAbsoluteReg32(FoxBCRegister reg); + void EmitJumpCallAbsolute(uint32 position); + void EmitJumpReturnToCaller(); + void EmitJumpCallExternal(FoxHash hashed_name); + + void EmitMoveInt32(FoxBCRegister reg, uint32 value); + + void EmitParamsStart(); + void EmitType(FoxValue::ValueType type); + + uint32 EmitDataString(char* str, uint16 length); + + FoxBCRegister EmitBinop(FoxAstBinop* binop, FoxBytecodeVarHandle* handle); + + FoxBCRegister EmitRhs(FoxAstNode* rhs, RhsMode mode, FoxBytecodeVarHandle* handle); + + FoxBCRegister EmitLiteralInt(FoxAstLiteral* literal, RhsMode mode, FoxBytecodeVarHandle* handle); + FoxBCRegister EmitLiteralString(FoxAstLiteral* literal, RhsMode mode, FoxBytecodeVarHandle* handle); + + + void WriteOp(uint8 base_op, uint8 spec_op); + void Write16(uint16 value); + void Write32(uint32 value); + + FoxBCRegister FindFreeRegister(); + + FoxBytecodeVarHandle* FindVarHandle(FoxHash hashed_name); + FoxBytecodeFunctionHandle* FindFunctionHandle(FoxHash hashed_name); + + void PrintBytecode(); + + void MarkRegisterUsed(FoxBCRegister reg); + void MarkRegisterFree(FoxBCRegister reg); + +public: + FoxMPPagedArray VarHandles; + std::vector FunctionHandles; + +private: + FoxRegisterFlag mRegsInUse = FX_REGFLAG_NONE; + + int64 mStackOffset = 0; + uint32 mStackSize = 0; + + uint16 mScopeIndex = 0; +}; + +class FoxBCPrinter +{ +public: + FoxBCPrinter(FoxMPPagedArray& bytecode) + { + mBytecode = bytecode; + mBytecode.DoNotDestroy = true; + } + + void Print(); + void PrintOp(); + + +private: + uint16 Read16(); + uint32 Read32(); + + void DoPush(char* s, uint8 op_base, uint8 op_spec); + void DoPop(char* s, uint8 op_base, uint8 op_spec); + void DoLoad(char* s, uint8 op_base, uint8 op_spec); + void DoArith(char* s, uint8 op_base, uint8 op_spec); + void DoSave(char* s, uint8 op_base, uint8 op_spec); + void DoJump(char* s, uint8 op_base, uint8 op_spec); + void DoData(char* s, uint8 op_base, uint8 op_spec); + void DoType(char* s, uint8 op_base, uint8 op_spec); + void DoMove(char* s, uint8 op_base, uint8 op_spec); + +private: + uint32 mBytecodeIndex = 0; + FoxMPPagedArray mBytecode; +}; + + +/////////////////////////////////////////// +// Bytecode VM +/////////////////////////////////////////// + +struct FoxVMCallFrame +{ + uint32 StartStackIndex = 0; +}; + +class FoxVM +{ +public: + FoxVM() = default; + + void Start(FoxMPPagedArray&& bytecode) + { + mBytecode = std::move(bytecode); + mPushedTypes.Create(64); + + Stack = FX_SCRIPT_ALLOC_MEMORY(uint8, 1024); + // mStackOffset = 0; + Registers[FX_REG_SP] = 0; + memset(Registers, 0, sizeof(Registers)); + + while (mPC < mBytecode.Size()) { + ExecuteOp(); + } + + PrintRegisters(); + } + + void PrintRegisters(); + + void Push16(uint16 value); + void Push32(uint32 value); + + uint32 Pop32(); + +private: + void ExecuteOp(); + + void DoPush(uint8 op_base, uint8 op_spec); + void DoPop(uint8 op_base, uint8 op_spec); + void DoLoad(uint8 op_base, uint8 op_spec); + void DoArith(uint8 op_base, uint8 op_spec); + void DoSave(uint8 op_base, uint8 op_spec); + void DoJump(uint8 op_base, uint8 op_spec); + void DoData(uint8 op_base, uint8 op_spec); + void DoType(uint8 op_base, uint8 op_spec); + void DoMove(uint8 op_base, uint8 op_spec); + + uint16 Read16(); + uint32 Read32(); + + FoxVMCallFrame& PushCallFrame(); + FoxVMCallFrame* GetCurrentCallFrame(); + void PopCallFrame(); + + FoxExternalFunc* FindExternalFunction(FoxHash hashed_name); + +public: + // NONE, X0, X1, X2, X3, RA, XR, SP + int32 Registers[FX_REG_SIZE]; + + uint8* Stack = nullptr; + + std::vector mExternalFuncs; + + FoxMPPagedArray mBytecode; + +private: + uint32 mPC = 0; + + + bool mIsInCallFrame = false; + + FoxVMCallFrame mCallFrames[8]; + int mCallFrameIndex = 0; + + bool mIsInParams = false; + FoxMPPagedArray mPushedTypes; + + FoxValue::ValueType mCurrentType = FoxValue::NONETYPE; +}; + +//////////////////////////////////////////////// +// Script Interpreter +//////////////////////////////////////////////// +#if 0 +class FoxInterpreter +{ +public: + FoxInterpreter() = default; + + void PushScope(); + void PopScope(); + + FoxVar* FindVar(FoxHash hashed_name); + FoxFunction* FindFunction(FoxHash hashed_name); + FoxExternalFunc* FindExternalFunction(FoxHash hashed_name); + + /** + * @brief Evaluates and gets the immediate value if `value` is a reference, or returns the value if it is already immediate. + * @param value The value to query from + * @return the immediate(literal) value + */ + const FoxValue& GetImmediateValue(const FoxValue& value); + + void DefineExternalVar(const char* type, const char* name, const FoxValue& value); + +private: + friend class FoxConfigScript; + void Create(FoxAstBlock* root_block); + + void Visit(FoxAstNode* node); + + void Interpret(); + + FoxValue VisitExternalCall(FoxAstFunctionCall* call, FoxExternalFunc& func); + FoxValue VisitFunctionCall(FoxAstFunctionCall* call); + void VisitAssignment(FoxAstAssign* assign); + FoxValue VisitRhs(FoxAstNode* node); + + bool CheckExternalCallArgs(FoxAstFunctionCall* call, FoxExternalFunc& func); + + + +private: + FoxAstNode* mRootBlock = nullptr; + + bool mInCommandMode = false; + + std::vector mExternalFuncs; + + FoxMPPagedArray mScopes; + FoxScope* mCurrentScope = nullptr; +}; + +#endif +///////////////////////////////////// +// Bytecode to x86 Transpiler +///////////////////////////////////// + + +class FoxTranspilerX86 +{ +public: + FoxTranspilerX86(FoxMPPagedArray& bytecode) + { + mBytecode = bytecode; + mBytecode.DoNotDestroy = true; + } + + void Print(); + void PrintOp(); + + +private: + uint16 Read16(); + uint32 Read32(); + + void DoPush(char* s, uint8 op_base, uint8 op_spec); + void DoPop(char* s, uint8 op_base, uint8 op_spec); + void DoLoad(char* s, uint8 op_base, uint8 op_spec); + void DoArith(char* s, uint8 op_base, uint8 op_spec); + void DoSave(char* s, uint8 op_base, uint8 op_spec); + void DoJump(char* s, uint8 op_base, uint8 op_spec); + void DoData(char* s, uint8 op_base, uint8 op_spec); + void DoType(char* s, uint8 op_base, uint8 op_spec); + void DoMove(char* s, uint8 op_base, uint8 op_spec); + + + void StrOut(const char* fmt, ...); + +private: + uint32 mBytecodeIndex = 0; + + uint32 mSizePushedInFunction = 0; + bool mIsInFunction = false; + + int mTextIndent = 0; + + FoxMPPagedArray mBytecode; +}; + + +//////////////////////////////////////////// +// IR Emitter +//////////////////////////////////////////// + + +#include "FoxScriptBytecode.hpp" + +class FoxIREmitter +{ +public: + FoxIREmitter() = default; + + void BeginEmitting(FoxAstNode* node); + void Emit(FoxAstNode* node); + + enum RhsMode + { + RHS_FETCH_TO_REGISTER, + + /** + * @brief Pushes the value to the stack, assuming that the value does not exist yet. + */ + RHS_DEFINE_IN_MEMORY, + + RHS_ASSIGN_TO_HANDLE, + }; + + static const char* GetRegisterName(FoxIRRegister reg); + + FoxMPPagedArray mBytecode {}; + + enum VarDeclareMode + { + DECLARE_DEFAULT, + DECLARE_NO_EMIT, + }; + + +private: + void EmitBlock(FoxAstBlock* block, bool ignore_function_definitions = false); + void EmitFunction(FoxAstFunctionDecl* function); + void EmitFunctionDefinitionsInBlock(FoxAstBlock* block); + void DoFunctionCall(FoxAstFunctionCall* call); + FoxBytecodeVarHandle* DoVarDeclare(FoxAstVarDecl* decl, VarDeclareMode mode = DECLARE_DEFAULT); + void EmitAssign(FoxAstAssign* assign); + FoxBytecodeVarHandle* DefineAndFetchParam(FoxAstNode* param_decl_node); + FoxBytecodeVarHandle* DefineReturnVar(FoxAstVarDecl* decl); + + FoxIRRegister EmitVarFetch(FoxAstVarRef* ref, RhsMode mode); + + uint16 GetSizeOfType(FoxTokenizer::Token* type); + + void DoLoad(uint32 stack_offset, FoxIRRegister output_reg, bool force_absolute = false); + void DoSaveInt32(uint32 stack_offset, uint32 value, bool force_absolute = false); + void DoSaveReg32(uint32 stack_offset, FoxIRRegister reg, bool force_absolute = false); + + void EmitPush32(uint32 value); + void EmitPush32r(FoxIRRegister reg); + + void EmitStackAlloc(uint16 size); + + void EmitPop32(FoxIRRegister output_reg); + + void EmitLoad32(int offset, FoxIRRegister output_reg); + void EmitLoadAbsolute32(uint32 position, FoxIRRegister output_reg); + + void EmitSave32(int16 offset, uint32 value); + void EmitSaveReg32(int16 offset, FoxIRRegister reg); + + void EmitSaveAbsolute32(uint32 offset, uint32 value); + void EmitSaveAbsoluteReg32(uint32 offset, FoxIRRegister reg); + + void EmitJumpRelative(uint16 offset); + void EmitJumpAbsolute(uint32 position); + void EmitJumpAbsoluteReg32(FoxIRRegister reg); + void EmitJumpCallAbsolute(uint32 position); + + void EmitJumpReturnToCaller(); + void EmitJumpReturnToCallerReg32(FoxIRRegister reg); + void EmitJumpReturnToCallerInt32(int32 value); + + void EmitJumpCallExternal(FoxHash hashed_name); + + void EmitVariableGetInt32(uint16 var_index, FoxIRRegister dest_reg); + void EmitVariableSetInt32(uint16 var_index, int32 value); + void EmitVariableSetReg32(uint16 var_index, FoxIRRegister reg); + + void EmitMoveInt32(FoxIRRegister reg, uint32 value); + + void EmitParamsStart(); + void EmitType(FoxValue::ValueType type); + + uint32 EmitDataString(char* str, uint16 length); + + FoxIRRegister EmitBinop(FoxAstBinop* binop, FoxBytecodeVarHandle* handle); + + FoxIRRegister EmitRhs(FoxAstNode* rhs, RhsMode mode, FoxBytecodeVarHandle* handle); + + FoxIRRegister EmitLiteralInt(FoxAstLiteral* literal, RhsMode mode, FoxBytecodeVarHandle* handle); + FoxIRRegister EmitLiteralString(FoxAstLiteral* literal, RhsMode mode, FoxBytecodeVarHandle* handle); + + void EmitMarker(IrSpecMarker spec); + + void WriteOp(uint8 base_op, uint8 spec_op); + void Write16(uint16 value); + void Write32(uint32 value); + + FoxIRRegister FindFreeReg32(); + FoxIRRegister FindFreeReg64(); + + FoxBytecodeVarHandle* FindVarHandle(FoxHash hashed_name); + FoxBytecodeFunctionHandle* FindFunctionHandle(FoxHash hashed_name); + + void PrintBytecode(); + + void MarkRegisterUsed(FoxIRRegister reg); + void MarkRegisterFree(FoxIRRegister reg); + + bool DoesNodeBranch(FoxAstNode* node); + +public: + FoxMPPagedArray VarHandles; + std::vector FunctionHandles; + +private: + FoxRegisterFlag mRegsInUse = FX_REGFLAG_NONE; + + int64 mStackOffset = 0; + uint32 mStackSize = 0; + + uint16 mVarsInScope = 0; + + uint16 mScopeIndex = 0; + + bool mEntryPointEmitted = false; +}; + + +class FoxIRPrinter +{ +public: + FoxIRPrinter(FoxMPPagedArray& bytecode) + { + mBytecode = bytecode; + mBytecode.DoNotDestroy = true; + } + + void Print(); + void PrintOp(); + + +private: + uint16 Read16(); + uint32 Read32(); + + void DoPush(char* s, uint8 op_base, uint8 op_spec); + void DoPop(char* s, uint8 op_base, uint8 op_spec); + void DoLoad(char* s, uint8 op_base, uint8 op_spec); + void DoArith(char* s, uint8 op_base, uint8 op_spec); + void DoSave(char* s, uint8 op_base, uint8 op_spec); + void DoJump(char* s, uint8 op_base, uint8 op_spec); + void DoData(char* s, uint8 op_base, uint8 op_spec); + void DoType(char* s, uint8 op_base, uint8 op_spec); + void DoMove(char* s, uint8 op_base, uint8 op_spec); + void DoMarker(char* s, uint8 op_base, uint8 op_spec); + void DoVariable(char* s, uint8 op_base, uint8 op_spec); + +private: + uint32 mBytecodeIndex = 0; + FoxMPPagedArray mBytecode; +}; + + +struct FoxIRArm64Frame +{ + uint32 StackAllocated = 0; + uint32 UnAlignedStackAllocated = 0; + uint32 RegistersInUse = 0; + + bool HasBaselevelReturnStmt = false; +}; + + +enum FoxArm64Register +{ + Fox_Arm64_W0, + Fox_Arm64_W1, + Fox_Arm64_W2, + Fox_Arm64_W3, + Fox_Arm64_W4, + Fox_Arm64_W5, + Fox_Arm64_W6, + Fox_Arm64_W7, + Fox_Arm64_W8, + Fox_Arm64_W9, + Fox_Arm64_W10, + Fox_Arm64_W11, + Fox_Arm64_W12, + Fox_Arm64_W13, + Fox_Arm64_W14, + Fox_Arm64_W15, + + Fox_Arm64_None, +}; + +class FoxIRToArm64 +{ +public: + enum RegisterUsage + { + Usage_Parameters, + Usage_General, + Usage_Return, + }; + +public: + FoxIRToArm64(FoxMPPagedArray& bytecode) + { + mBytecode = bytecode; + mBytecode.DoNotDestroy = true; + } + + void Print(); + void PrintOp(); + + +private: + uint16 Read16(); + uint32 Read32(); + + void DoPush(char* s, uint8 op_base, uint8 op_spec); + void DoPop(char* s, uint8 op_base, uint8 op_spec); + void DoLoad(char* s, uint8 op_base, uint8 op_spec); + void DoArith(char* s, uint8 op_base, uint8 op_spec); + void DoSave(char* s, uint8 op_base, uint8 op_spec); + void DoJump(char* s, uint8 op_base, uint8 op_spec); + void DoData(char* s, uint8 op_base, uint8 op_spec); + void DoType(char* s, uint8 op_base, uint8 op_spec); + void DoMove(char* s, uint8 op_base, uint8 op_spec); + void DoMarker(char* s, uint8 op_base, uint8 op_spec); + void DoVariable(char* s, uint8 op_base, uint8 op_spec); + + void EmitFrameRestore(); + +private: + // void ResetFrame(); + + uint32 MakeValueFactorOf16(uint32 value); + + FoxArm64Register RegisterRequest(RegisterUsage usage); + bool IsRegisterInUse(FoxArm64Register reg); + void RegisterRelease(FoxArm64Register reg); + + FoxArm64Register GetArmRegFromIRReg(FoxIRRegister ir_reg); + + const char* GetRegisterName(FoxArm64Register reg); + + FoxIRArm64Frame* GetCurrentFrame(); + + FoxIRArm64Frame* FramePush(); + void FramePop(); + + +private: + uint32 mBytecodeIndex = 0; + FoxMPPagedArray mBytecode; + + uint32 PreFrameStackAllocation = 0; + FoxMPPagedArray mStackFrames; + + bool mEmitDefinitionAsEntryPoint = false; + bool mFunctionContainsBranches = false; +}; diff --git a/FoxScriptBytecode.hpp b/FoxScriptBytecode.hpp new file mode 100644 index 0000000..395adee --- /dev/null +++ b/FoxScriptBytecode.hpp @@ -0,0 +1,184 @@ +#pragma once + +#include "FoxScriptUtil.hpp" + + +/* +[BASE] [SPEC] +*/ + +enum OpBase : uint8 +{ + OpBase_Push = 1, + OpBase_Pop, + OpBase_Load, + OpBase_Arith, + OpBase_Save, + OpBase_Jump, + OpBase_Data, + OpBase_Type, + OpBase_Move, +}; + +enum OpSpecPush : uint8 +{ + OpSpecPush_Int32 = 1, // PUSH32 [imm] + OpSpecPush_Reg32, // PUSH32r [%r32] +}; + +enum OpSpecPop : uint8 +{ + OpSpecPop_Int32 = 1, // POP32 [%r32] +}; + +enum OpSpecLoad : uint8 +{ + OpSpecLoad_Int32 = 1, // LOAD32 [offset] [%r32] + OpSpecLoad_AbsoluteInt32, +}; + +enum OpSpecArith : uint8 +{ + OpSpecArith_Add = 1 // ADD [%r32] [%r32] +}; + +enum OpSpecSave : uint8 +{ + OpSpecSave_Int32 = 1, + OpSpecSave_Reg32, + OpSpecSave_AbsoluteInt32, + OpSpecSave_AbsoluteReg32 +}; + +enum OpSpecJump : uint8 +{ + OpSpecJump_Relative = 1, + OpSpecJump_Absolute, + OpSpecJump_AbsoluteReg32, + + OpSpecJump_CallAbsolute, + OpSpecJump_ReturnToCaller, + + OpSpecJump_CallExternal, +}; + +enum OpSpecData : uint8 +{ + OpSpecData_String = 1, + OpSpecData_ParamsStart, +}; + +enum OpSpecType : uint8 +{ + OpSpecType_Int = 1, + OpSpecType_String, +}; + +enum OpSpecMove : uint8 +{ + OpSpecMove_Int32 = 1, +}; + + +///////////////////////////////////////////// +// IR Bytecode +///////////////////////////////////////////// + + +enum IrBase : uint8 +{ + IrBase_Push = 1, + IrBase_Pop, + IrBase_Load, + IrBase_Arith, + IrBase_Save, + IrBase_Jump, + IrBase_Data, + IrBase_Type, + IrBase_Move, + IrBase_Marker, + + IrBase_Variable, +}; + +enum IrSpecPush : uint8 +{ + IrSpecPush_Int32 = 1, // PUSH32 [imm] + IrSpecPush_Reg32, // PUSH32r [%r32] + + IrSpecPush_StackAlloc, +}; + +enum IrSpecPop : uint8 +{ + IrSpecPop_Int32 = 1, // PIr32 [%r32] +}; + +enum IrSpecLoad : uint8 +{ + IrSpecLoad_Int32 = 1, // LOAD32 [offset] [%r32] + IrSpecLoad_AbsoluteInt32, +}; + +enum IrSpecArith : uint8 +{ + IrSpecArith_Add_Reg32 = 1 // ADD [%r32] [%r32] +}; + +enum IrSpecSave : uint8 +{ + IrSpecSave_Int32 = 1, + IrSpecSave_Reg32, + IrSpecSave_AbsoluteInt32, + IrSpecSave_AbsoluteReg32 +}; + +enum IrSpecJump : uint8 +{ + IrSpecJump_Relative = 1, + IrSpecJump_Absolute, + IrSpecJump_AbsoluteReg32, + + IrSpecJump_CallAbsolute, + + IrSpecJump_ReturnToCaller, + IrSpecJump_ReturnToCaller_Int32, + IrSpecJump_ReturnToCaller_Reg32, + + IrSpecJump_CallExternal, +}; + +enum IrSpecData : uint8 +{ + IrSpecData_String = 1, +}; + +enum IrSpecType : uint8 +{ + IrSpecType_Int = 1, + IrSpecType_String, +}; + +enum IrSpecMove : uint8 +{ + IrSpecMove_Int32 = 1, +}; + +enum IrSpecMarker : uint8 +{ + // Function frame ops + IrSpecMarker_FrameBegin = 1, + IrSpecMarker_FrameEnd, + + IrSpecMarker_ParamsBegin, + + IrSpecMarker_EntryPoint, + IrSpecMarker_FunctionBranches, +}; + +enum IrSpecVariable : uint8 +{ + IrSpecVariable_Set_Int32 = 1, + IrSpecVariable_Set_Reg32, + IrSpecVariable_Get_Int32, +}; diff --git a/FxScriptUtil.hpp b/FoxScriptUtil.hpp similarity index 74% rename from FxScriptUtil.hpp rename to FoxScriptUtil.hpp index 2acb640..d99e872 100644 --- a/FxScriptUtil.hpp +++ b/FoxScriptUtil.hpp @@ -1,8 +1,7 @@ #pragma once -#include #include - +#include #include // #include @@ -13,25 +12,25 @@ #ifdef FX_SCRIPT_USE_MEMPOOL -#include "FxMemPool.hpp" +#include "FoxMemPool.hpp" -#define FX_SCRIPT_ALLOC_MEMORY(ptrtype_, size_) FxMemPool::Alloc(size_) -#define FX_SCRIPT_ALLOC_NODE(nodetype_) FxMemPool::Alloc(sizeof(nodetype_)) -#define FX_SCRIPT_FREE(ptrtype_, ptr_) FxMemPool::Free(ptr_) +#define FX_SCRIPT_ALLOC_MEMORY(ptrtype_, size_) FoxMemPool::Alloc(size_) +#define FX_SCRIPT_ALLOC_NODE(nodetype_) FoxMemPool::Alloc(sizeof(nodetype_)) +#define FX_SCRIPT_FREE(ptrtype_, ptr_) FoxMemPool::Free(ptr_) #else // #define FX_SCRIPT_ALLOC_MEMORY(ptrtype_, size_) new ptrtype_[size_] -#define FX_SCRIPT_ALLOC_MEMORY(ptrtype_, size_) FxScriptAllocMemory(size_) -#define FX_SCRIPT_ALLOC_NODE(nodetype_) FxScriptAllocMemory(sizeof(nodetype_)) -#define FX_SCRIPT_FREE(ptrtype_, ptr_) FxScriptFreeMemory(ptr_) +#define FX_SCRIPT_ALLOC_MEMORY(ptrtype_, size_) FoxAllocMemory(size_) +#define FX_SCRIPT_ALLOC_NODE(nodetype_) FoxAllocMemory(sizeof(nodetype_)) +#define FX_SCRIPT_FREE(ptrtype_, ptr_) FoxFreeMemory(ptr_) #include #endif template -T* FxScriptAllocMemory(size_t size) +T* FoxAllocMemory(size_t size) { T* ptr = reinterpret_cast(malloc(size)); if constexpr (std::is_constructible_v) { @@ -42,7 +41,7 @@ T* FxScriptAllocMemory(size_t size) } template -void FxScriptFreeMemory(T* ptr) +void FoxFreeMemory(T* ptr) { if constexpr (std::is_destructible_v) { ptr->~T(); @@ -76,7 +75,7 @@ typedef double float64; ///////////////////////////// -class FxUtil +class FoxUtil { public: static FILE* FileOpen(const char* path, const char* mode) @@ -93,14 +92,14 @@ class FxUtil #define FX_HASH_FNV1A_SEED 0x811C9DC5 #define FX_HASH_FNV1A_PRIME 0x01000193 -using FxHash = uint32; +using FoxHash = uint32; /** * Hashes a string at compile time using FNV-1a. * * Source to algorithm: http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-param */ -inline constexpr FxHash FxHashStr(const char *str) +inline constexpr FoxHash FoxHashStr(const char* str) { uint32 hash = FX_HASH_FNV1A_SEED; @@ -124,7 +123,7 @@ inline constexpr FxHash FxHashStr(const char *str) #endif template -void FxPanic(const char* const module, const char* fmt, T first, Types... items) +void FoxPanic(const char* const module, const char* fmt, T first, Types... items) { // printf(fmt, items...); ((std::cout << items), ...); @@ -138,7 +137,7 @@ void FxPanic(const char* const module, const char* fmt, T first, Types... items) * * Source to algorithm: http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-param */ -inline constexpr FxHash FxHashStr(const char *str, uint32 length) +inline constexpr FoxHash FoxHashStr(const char* str, uint32 length) { uint32 hash = FX_HASH_FNV1A_SEED; diff --git a/FxTokenizer.hpp b/FoxTokenizer.hpp similarity index 91% rename from FxTokenizer.hpp rename to FoxTokenizer.hpp index 0c588d8..31e9c92 100644 --- a/FxTokenizer.hpp +++ b/FoxTokenizer.hpp @@ -1,14 +1,14 @@ #pragma once -#include "FxScriptUtil.hpp" -#include "FxMPPagedArray.hpp" +#include "FoxMPPagedArray.hpp" +#include "FoxScriptUtil.hpp" +#include #include #include -#include #include -class FxTokenizer +class FoxTokenizer { private: public: @@ -25,7 +25,8 @@ class FxTokenizer }; - enum TokenType { + enum TokenType + { Unknown, Identifier, @@ -60,33 +61,23 @@ class FxTokenizer static const char* GetTypeName(TokenType type) { const char* type_names[] = { - "Unknown", - "Identifier", + "Unknown", "Identifier", - "String", - "Integer", - "Float", + "String", "Integer", "Float", "Equals", - "LParen", - "RParen", + "LParen", "RParen", - "LBracket", - "RBracket", + "LBracket", "RBracket", - "LBrace", - "RBrace", + "LBrace", "RBrace", - "Plus", - "Dollar", - "Minus", + "Plus", "Dollar", "Minus", "Question", - "Dot", - "Comma", - "Semicolon", + "Dot", "Comma", "Semicolon", "DocComment", }; @@ -98,10 +89,11 @@ class FxTokenizer return type_names[type]; } - enum class IsNumericResult { + enum class IsNumericResult + { NaN, Integer, - Fractional + Frfunctional }; struct Token @@ -109,14 +101,14 @@ class FxTokenizer char* Start = nullptr; char* End = nullptr; - FxHash Hash = 0; + FoxHash Hash = 0; TokenType Type = TokenType::Unknown; uint32 Length = 0; uint16 FileColumn = 0; uint32 FileLine = 0; - void Print(bool no_newline=false) const + void Print(bool no_newline = false) const { printf("Token: (T:%-10s) {%.*s} %c", GetTypeName(Type), Length, Start, (no_newline) ? ' ' : '\n'); } @@ -136,7 +128,7 @@ class FxTokenizer char* str = static_cast(FX_SCRIPT_ALLOC_MEMORY(char, (Length + 1))); if (str == nullptr) { - FxPanic("FxTokenizer", "Error allocating heap string!", 0); + FoxPanic("FoxTokenizer", "Error allocating heap string!", 0); return nullptr; } @@ -146,12 +138,12 @@ class FxTokenizer return str; } - FxHash GetHash() + FoxHash GetHash() { if (Hash != 0) { return Hash; } - return (Hash = FxHashStr(Start, Length)); + return (Hash = FoxHashStr(Start, Length)); } IsNumericResult IsNumeric() const @@ -163,13 +155,13 @@ class FxTokenizer for (int i = 0; i < Length; i++) { ch = Start[i]; - // If there is a number preceding the dot then we are a fractional + // If there is a number preceding the dot then we are a frfunctional if (ch == '.' && result != IsNumericResult::NaN) { - result = IsNumericResult::Fractional; + result = IsNumericResult::Frfunctional; continue; } - if ((ch >= '0' && ch <= '9') ) { + if ((ch >= '0' && ch <= '9')) { // If no numbers have been found yet then set to integer if (result == IsNumericResult::NaN) { result = IsNumericResult::Integer; @@ -188,7 +180,7 @@ class FxTokenizer int64 ToInt() const { char buffer[32]; - + std::strncpy(buffer, Start, Length); buffer[Length] = 0; @@ -206,7 +198,7 @@ class FxTokenizer return strtof(buffer, &end); } - bool operator == (const char* str) const + bool operator==(const char* str) const { return !strncmp(Start, str, Length); } @@ -224,10 +216,9 @@ class FxTokenizer } }; - FxTokenizer() = delete; + FoxTokenizer() = delete; - FxTokenizer(char* data, uint32 buffer_size) - : mData(data), mDataEnd(data + buffer_size), mStartOfLine(data) + FoxTokenizer(char* data, uint32 buffer_size) : mData(data), mDataEnd(data + buffer_size), mStartOfLine(data) { } @@ -242,7 +233,7 @@ class FxTokenizer switch (is_numeric) { case IsNumericResult::Integer: return TokenType::Integer; - case IsNumericResult::Fractional: + case IsNumericResult::Frfunctional: return TokenType::Float; case IsNumericResult::NaN: break; @@ -366,7 +357,8 @@ class FxTokenizer } // Skip spaces and tabs - while ((ch = *(data)) && (ch == ' ' || ch == '\t')) ++data; + while ((ch = *(data)) && (ch == ' ' || ch == '\t')) + ++data; // Check if this is a string if (ch != '"') { @@ -429,7 +421,7 @@ class FxTokenizer void IncludeFile(char* path) { - FILE* fp = FxUtil::FileOpen(path, "rb"); + FILE* fp = FoxUtil::FileOpen(path, "rb"); if (!fp) { printf("Could not open include file '%s'\n", path); return; @@ -451,7 +443,7 @@ class FxTokenizer // Restore back to previous state RestoreState(); - //FxMemPool::Free(include_data); + // FoxMemPool::Free(include_data); } void TryReadInternalCall() @@ -619,7 +611,7 @@ class FxTokenizer return (token.Start - mData); } - FxMPPagedArray& GetTokens() + FoxMPPagedArray& GetTokens() { return mTokens; } @@ -642,7 +634,6 @@ class FxTokenizer mFileLine = mSavedState.FileLine; mStartOfLine = mSavedState.StartOfLine; - } private: @@ -662,5 +653,5 @@ class FxTokenizer uint32 mFileLine = 0; char* mStartOfLine = nullptr; - FxMPPagedArray mTokens; + FoxMPPagedArray mTokens; }; diff --git a/FxScript.cpp b/FxScript.cpp deleted file mode 100644 index 62cebcf..0000000 --- a/FxScript.cpp +++ /dev/null @@ -1,3564 +0,0 @@ -#include "FxScript.hpp" - -#include -#include - -#include "FxScriptUtil.hpp" - -#define FX_SCRIPT_SCOPE_GLOBAL_VARS_START_SIZE 32 -#define FX_SCRIPT_SCOPE_LOCAL_VARS_START_SIZE 16 - -#define FX_SCRIPT_SCOPE_GLOBAL_ACTIONS_START_SIZE 32 -#define FX_SCRIPT_SCOPE_LOCAL_ACTIONS_START_SIZE 32 - -using Token = FxTokenizer::Token; -using TT = FxTokenizer::TokenType; - -FxScriptValue FxScriptValue::None{}; - -void FxConfigScript::LoadFile(const char* path) -{ - FILE* fp = FxUtil::FileOpen(path, "rb"); - if (fp == nullptr) { - printf("[ERROR] Could not open config file at '%s'\n", path); - return; - } - - std::fseek(fp, 0, SEEK_END); - size_t file_size = std::ftell(fp); - std::rewind(fp); - - mFileData = FX_SCRIPT_ALLOC_MEMORY(char, file_size); - - size_t read_size = std::fread(mFileData, 1, file_size, fp); - if (read_size != file_size) { - printf("[WARNING] Error reading all data from config file at '%s' (read=%zu, size=%zu)\n", path, read_size, file_size); - } - - FxTokenizer tokenizer(mFileData, read_size); - tokenizer.Tokenize(); - - - fclose(fp); - - mTokens = std::move(tokenizer.GetTokens()); - - /*for (const auto& token : mTokens) { - token.Print(); - }*/ - - mScopes.Create(8); - mCurrentScope = mScopes.Insert(); - mCurrentScope->Vars.Create(FX_SCRIPT_SCOPE_GLOBAL_VARS_START_SIZE); - mCurrentScope->Actions.Create(FX_SCRIPT_SCOPE_GLOBAL_ACTIONS_START_SIZE); - - CreateInternalVariableTokens(); -} - -Token& FxConfigScript::GetToken(int offset) -{ - const uint32 idx = mTokenIndex + offset; - if (idx < 0 || idx >= mTokens.Size()) { - printf("SOMETHING IS MISSING\n"); - } - assert(idx >= 0 && idx <= mTokens.Size()); - return mTokens[idx]; -} - -Token& FxConfigScript::EatToken(TT token_type) -{ - Token& token = GetToken(); - if (token.Type != token_type) { - printf("[ERROR] %u:%u: Unexpected token type %s when expecting %s!\n", token.FileLine, token.FileColumn, FxTokenizer::GetTypeName(token.Type), FxTokenizer::GetTypeName(token_type)); - mHasErrors = true; - } - ++mTokenIndex; - return token; -} - -Token* FxConfigScript::CreateTokenFromString(FxTokenizer::TokenType type, const char* text) -{ - const uint32 name_len = strlen(text); - - // Create (fabricate..) the name token - Token* token = FX_SCRIPT_ALLOC_NODE(Token); - token->Start = FX_SCRIPT_ALLOC_MEMORY(char, name_len); - token->End = token->Start + name_len; - token->Length = name_len; - token->Type = type; - - // Copy the name to the buffer in the name token - memcpy(token->Start, text, name_len); - - return token; -} - -void FxConfigScript::CreateInternalVariableTokens() -{ - mTokenReturnVar = CreateTokenFromString(TT::Identifier, FX_SCRIPT_VAR_RETURN_VAL); -} - -void PrintDocCommentExample(FxTokenizer::Token* comment, int example_tag_length, bool is_command_mode) -{ - char* start = comment->Start + example_tag_length; - - for (int i = 0; i < comment->Length - example_tag_length; i++) { - char ch = start[i]; - - if (!is_command_mode) { - putchar(ch); - continue; - } - - // If we are in command mode, print the example using the command syntax - if (ch == '(' || ch == ')' || ch == ',') { - putchar(' '); - continue; - } - - if (ch == ' ' || ch == '\t' || ch == ';') { - continue; - } - - putchar(ch); - } - - puts(""); -} - -void PrintDocComment(FxTokenizer::Token* comment, bool is_command_mode) -{ - const char* example_tag = "EX: "; - const int example_tag_length = 4; - - char* start = comment->Start; - - if (comment->Length > example_tag_length && !strncmp(start, example_tag, example_tag_length)) { - printf("\n\t-> Script : "); - PrintDocCommentExample(comment, example_tag_length, false); - - printf("\t-> Command: "); - PrintDocCommentExample(comment, example_tag_length, true); - - return; - } - - printf("%.*s\n", comment->Length, start); -} - -FxAstNode* FxConfigScript::TryParseKeyword(FxAstBlock* parent_block) -{ - if (mTokenIndex >= mTokens.Size()) { - return nullptr; - } - - Token& tk = GetToken(); - FxHash hash = tk.GetHash(); - - // action [name] ( < [arg type] [arg name] ...> ) { } - constexpr FxHash kw_action = FxHashStr("fn"); - - // local [type] [name] ; - constexpr FxHash kw_local = FxHashStr("local"); - - // global [type] [name] ; - constexpr FxHash kw_global = FxHashStr("global"); - - // return ; - constexpr FxHash kw_return = FxHashStr("return"); - - // help [name of action] ; - constexpr FxHash kw_help = FxHashStr("help"); - - if (hash == kw_action) { - EatToken(TT::Identifier); - //ParseActionDeclare(); - return ParseActionDeclare(); - } - if (hash == kw_local) { - EatToken(TT::Identifier); - return ParseVarDeclare(); - } - if (hash == kw_global) { - EatToken(TT::Identifier); - return ParseVarDeclare(&mScopes[0]); - } - if (hash == kw_return) { - EatToken(TT::Identifier); - - if (GetToken().Type != TT::Semicolon) { - // There is a value that follows, get the value - FxAstNode* rhs = ParseRhs(); - - // Assign the return value to __ReturnVal__ - - FxAstVarRef* var_ref = FX_SCRIPT_ALLOC_NODE(FxAstVarRef); - var_ref->Name = mTokenReturnVar; - - FxAstAssign* assign = FX_SCRIPT_ALLOC_NODE(FxAstAssign); - assign->Var = var_ref; - assign->Rhs = rhs; - - parent_block->Statements.push_back(assign); - } - - FxAstReturn* ret = FX_SCRIPT_ALLOC_NODE(FxAstReturn); - return ret; - } - if (hash == kw_help) { - EatToken(TT::Identifier); - - FxTokenizer::Token& func_ref = EatToken(TT::Identifier); - - FxScriptAction* action = FindAction(func_ref.GetHash()); - - if (action) { - for (FxAstDocComment* comment : action->Declaration->DocComments) { - printf("[DOC] %.*s: ", action->Name->Length, action->Name->Start); - PrintDocComment(comment->Comment, mInCommandMode); - } - } - - return nullptr; - } - - return nullptr; -} - -void FxConfigScript::PushScope() -{ - FxScriptScope* current = mCurrentScope; - - FxScriptScope* new_scope = mScopes.Insert(); - new_scope->Parent = current; - new_scope->Vars.Create(FX_SCRIPT_SCOPE_LOCAL_VARS_START_SIZE); - new_scope->Actions.Create(FX_SCRIPT_SCOPE_LOCAL_ACTIONS_START_SIZE); - - mCurrentScope = new_scope; -} - -void FxConfigScript::PopScope() -{ - FxScriptScope* new_scope = mCurrentScope->Parent; - mScopes.RemoveLast(); - - assert(new_scope == &mScopes.GetLast()); - - mCurrentScope = new_scope; -} - -FxAstVarDecl* FxConfigScript::InternalVarDeclare(FxTokenizer::Token* name_token, FxTokenizer::Token* type_token, FxScriptScope* scope) -{ - if (scope == nullptr) { - scope = mCurrentScope; - } - - // Allocate the declaration node - FxAstVarDecl* node = FX_SCRIPT_ALLOC_NODE(FxAstVarDecl); - - node->Name = name_token; - node->Type = type_token; - node->DefineAsGlobal = (scope == &mScopes[0]); - - // Push the variable to the scope - FxScriptVar var { name_token, type_token, scope }; - scope->Vars.Insert(var); - - return node; -} - -FxAstVarDecl* FxConfigScript::ParseVarDeclare(FxScriptScope* scope) -{ - if (scope == nullptr) { - scope = mCurrentScope; - } - - Token& type = EatToken(TT::Identifier); - Token& name = EatToken(TT::Identifier); - - FxAstVarDecl* node = FX_SCRIPT_ALLOC_NODE(FxAstVarDecl); - - node->Name = &name; - node->Type = &type; - node->DefineAsGlobal = (scope == &mScopes[0]); - - FxScriptVar var { &type, &name, scope }; - - node->Assignment = TryParseAssignment(node->Name); - /*if (node->Assignment) { - var.Value = node->Assignment->Value; - }*/ - scope->Vars.Insert(var); - - return node; -} - -//FxScriptVar& FxConfigScript::ParseVarDeclare() -//{ -// Token& type = EatToken(TT::Identifier); -// Token& name = EatToken(TT::Identifier); -// -// FxScriptVar var{ name.GetHash(), &type, &name }; -// -// TryParseAssignment(var); -// -// mCurrentScope->Vars.Insert(var); -// -// return mCurrentScope->Vars.GetLast(); -//} - -FxScriptVar* FxConfigScript::FindVar(FxHash hashed_name) -{ - FxScriptScope* scope = mCurrentScope; - - while (scope) { - FxScriptVar* var = scope->FindVarInScope(hashed_name); - if (var) { - return var; - } - - scope = scope->Parent; - } - - return nullptr; -} - -FxScriptExternalFunc* FxConfigScript::FindExternalAction(FxHash hashed_name) -{ - for (FxScriptExternalFunc& func : mExternalFuncs) { - if (func.HashedName == hashed_name) { - return &func; - } - } - - return nullptr; -} - -FxScriptAction* FxConfigScript::FindAction(FxHash hashed_name) -{ - FxScriptScope* scope = mCurrentScope; - - while (scope) { - FxScriptAction* var = scope->FindActionInScope(hashed_name); - if (var) { - return var; - } - - scope = scope->Parent; - } - - return nullptr; -} - -void FxConfigScript::Execute(FxScriptVM& vm) -{ - DefineDefaultExternalFunctions(); - - mRootBlock = Parse(); - - // If there are errors, exit early - if (mHasErrors || mRootBlock == nullptr) { - return; - } - printf("\n=====\n"); - FxScriptBCEmitter emitter; - emitter.BeginEmitting(mRootBlock); - - printf("\n=====\n"); - - FxScriptBCPrinter printer(emitter.mBytecode); - printer.Print(); - - printf("\n=====\n"); - - FxScriptTranspilerX86 transpiler(emitter.mBytecode); - transpiler.Print(); - - printf("\n=====\n"); - - for (FxScriptBytecodeVarHandle& handle : emitter.VarHandles) { - printf("Var(%u) AT %lld\n", handle.HashedName, handle.Offset); - } - - //return; - - vm.mExternalFuncs = mExternalFuncs; - vm.Start(std::move(emitter.mBytecode)); - - for (FxScriptBytecodeVarHandle& handle : emitter.VarHandles) { - printf("Var(%u) AT %lld -> %u\n", handle.HashedName, handle.Offset, vm.Stack[handle.Offset]); - } - - return; - /* - interpreter.Create(mRootBlock); - - // Copy any external variable declarations from the parser to the interpreter - FxScriptScope& global_scope = mScopes[0]; - FxScriptScope& interpreter_global_scope = interpreter.mScopes[0]; - - for (FxScriptVar& var : global_scope.Vars) { - if (var.IsExternal) { - interpreter_global_scope.Vars.Insert(var); - } - } - - interpreter.mExternalFuncs = mExternalFuncs; - - interpreter.Interpret();*/ -} - -bool FxConfigScript::ExecuteUserCommand(const char* command, FxScriptInterpreter& interpreter) -{ - mHasErrors = false; - - // If there are errors, exit early - if (mRootBlock == nullptr) { - return false; - } - - uint32 new_token_index = mTokens.Size(); - - uint32 old_token_index = mTokenIndex; - - { - uint32 length_of_command = strlen(command); - - char* data_buffer = FX_SCRIPT_ALLOC_MEMORY(char, length_of_command + 1); - memcpy(data_buffer, command, length_of_command); - - FxTokenizer tokenizer(data_buffer, length_of_command); - tokenizer.Tokenize(); - - for (FxTokenizer::Token& token : tokenizer.GetTokens()) { - //token.Print(); - mTokens.Insert(token); - } - - /*for (FxTokenizer::Token& token : mTokens) { - token.Print(); - }*/ - } - - mTokenIndex = new_token_index; - mInCommandMode = true; - - FxAstBlock* root_block = FX_SCRIPT_ALLOC_NODE(FxAstBlock); - - FxAstNode* statement; - while ((statement = ParseStatementAsCommand(root_block))) { - root_block->Statements.push_back(statement); - } - - //FxAstNode* node = ParseStatementAsCommand(); - - // Revert the current state if there have been errors - if (root_block == nullptr) { - printf("Ignoring state...\n"); - // Get the size of the tokens - // int current_token_index = mTokens.Size(); - for (int i = new_token_index; i < new_token_index; i++) { - mTokens.RemoveLast(); - } - - mTokenIndex = old_token_index; - - mInCommandMode = false; - - // Since we have reverted state, clear errors flag - mHasErrors = false; - - return false; - } - -#if 0 - - // Run the new block - { - interpreter.mInCommandMode = true; - interpreter.Visit(root_block); - interpreter.mInCommandMode = false; - } -#endif - mInCommandMode = false; - - return true; -} - -FxScriptValue FxConfigScript::ParseValue() -{ - Token& token = GetToken(); - TT token_type = token.Type; - FxScriptValue value; - - if (token_type == TT::Identifier) { - FxScriptVar* var = FindVar(token.GetHash()); - - if (var) { - value.Type = FxScriptValue::REF; - - FxAstVarRef* var_ref = FX_SCRIPT_ALLOC_NODE(FxAstVarRef); - var_ref->Name = var->Name; - var_ref->Scope = var->Scope; - - value.ValueRef = var_ref; - - EatToken(TT::Identifier); - - return value; - } - else { - // We cannot find the definition for the variable, assume that it is an external variable that will be defined - // during the interpret stage. - - printf("ERROR: Undefined reference to variable "); - token.Print(); - printf("\n"); - - //printf("Undefined reference to variable \"%.*s\"! (Hash:%u)\n", token.Length, token.Start, token.GetHash()); - EatToken(TT::Identifier); - } - } - - switch (token_type) { - case TT::Integer: - EatToken(TT::Integer); - value.Type = FxScriptValue::INT; - value.ValueInt = token.ToInt(); - break; - case TT::Float: - EatToken(TT::Float); - value.Type = FxScriptValue::FLOAT; - value.ValueFloat = token.ToFloat(); - break; - case TT::String: - EatToken(TT::String); - value.Type = FxScriptValue::STRING; - value.ValueString = token.GetHeapStr(); - break; - default:; - } - - return value; -} - -#define RETURN_IF_NO_TOKENS(rval_) \ - { \ - if (mTokenIndex >= mTokens.Size()) \ - return (rval_); \ - } - -static bool IsTokenTypeLiteral(FxTokenizer::TokenType type) -{ - return (type == TT::Integer || type == TT::Float || type == TT::String); -} - -FxAstNode* FxConfigScript::ParseRhs() -{ - - RETURN_IF_NO_TOKENS(nullptr); - - bool has_parameters = false; - - if (mTokenIndex + 1 < mTokens.Size()) { - TT next_token_type = GetToken(1).Type; - has_parameters = next_token_type == TT::LParen; - - if (mInCommandMode) { - has_parameters = next_token_type == TT::Identifier || next_token_type == TT::Integer || next_token_type == TT::Float || next_token_type == TT::String; - } - } - - FxTokenizer::Token& token = GetToken(); - - FxAstNode* lhs = nullptr; - - if (token.Type == TT::Identifier) { - if (has_parameters) { - lhs = ParseActionCall(); - } - else { - FxScriptExternalFunc* external_action = FindExternalAction(token.GetHash()); - if (external_action != nullptr) { - lhs = ParseActionCall(); - } - - FxScriptAction* action = FindAction(token.GetHash()); - if (action != nullptr) { - lhs = ParseActionCall(); - } - - } - } - if (!lhs) { - if (IsTokenTypeLiteral(token.Type) || token.Type == TT::Identifier) { - FxAstLiteral* literal = FX_SCRIPT_ALLOC_NODE(FxAstLiteral); - - FxScriptValue value = ParseValue(); - literal->Value = value; - - lhs = literal; - } - else { - lhs = ParseRhs(); - } - } - - - // FxAstLiteral* literal = FX_SCRIPT_ALLOC_NODE(FxAstLiteral); - // literal->Value = value; - - TT op_type = GetToken(0).Type; - if (op_type == TT::Plus || op_type == TT::Minus) { - FxAstBinop* binop = FX_SCRIPT_ALLOC_NODE(FxAstBinop); - - binop->Left = lhs; - binop->OpToken = &EatToken(op_type); - binop->Right = ParseRhs(); - - return binop; - } - - return lhs; - - /* - RETURN_IF_NO_TOKENS(nullptr); - - bool has_parameters = false; - - if (mTokenIndex + 1 < mTokens.Size()) { - TT next_token_type = GetToken(1).Type; - has_parameters = next_token_type == TT::LParen; - - if (mInCommandMode) { - has_parameters = next_token_type == TT::Identifier || next_token_type == TT::Integer || next_token_type == TT::Float || next_token_type == TT::String; - } - } - - FxTokenizer::Token& token = GetToken(); - - FxAstNode* lhs = nullptr; - - if (token.Type == TT::Identifier) { - if (has_parameters) { - lhs = ParseActionCall(); - } - else { - FxScriptExternalFunc* external_action = FindExternalAction(token.GetHash()); - if (external_action != nullptr) { - return ParseActionCall(); - } - - FxScriptAction* action = FindAction(token.GetHash()); - if (action != nullptr) { - return ParseActionCall(); - } - - } - } - - else if (IsTokenTypeLiteral(token.Type) || token.Type == TT::Identifier) { - FxAstLiteral* literal = FX_SCRIPT_ALLOC_NODE(FxAstLiteral); - literal->Value = ParseValue(); - lhs = literal; - } - - TT op_type = GetToken(0).Type; - if (op_type == TT::Plus || op_type == TT::Minus) { - FxAstBinop* binop = FX_SCRIPT_ALLOC_NODE(FxAstBinop); - - binop->Left = lhs; - binop->OpToken = &EatToken(op_type); - binop->Right = ParseRhs(); - - return binop; - } - - return lhs; - */ -} - -FxAstAssign* FxConfigScript::TryParseAssignment(FxTokenizer::Token* var_name) -{ - if (GetToken().Type != TT::Equals) { - return nullptr; - } - - EatToken(TT::Equals); - - FxAstAssign* node = FX_SCRIPT_ALLOC_NODE(FxAstAssign); - - FxAstVarRef* var_ref = FX_SCRIPT_ALLOC_NODE(FxAstVarRef); - var_ref->Name = var_name; - var_ref->Scope = mCurrentScope; - node->Var = var_ref; - - //node->Value = ParseValue(); - node->Rhs = ParseRhs(); - - return node; -} - -void FxConfigScript::DefineExternalVar(const char* type, const char* name, const FxScriptValue& value) -{ - Token* name_token = FX_SCRIPT_ALLOC_MEMORY(Token, sizeof(Token)); - Token* type_token = FX_SCRIPT_ALLOC_MEMORY(Token, sizeof(Token)); - - { - const uint32 type_len = strlen(type); - - char* type_buffer = FX_SCRIPT_ALLOC_MEMORY(char, (type_len + 1)); - std::strcpy(type_buffer, type); - - type_token->Start = type_buffer; - type_token->Type = TT::Identifier; - type_token->Start[type_len] = 0; - type_token->Length = type_len + 1; - } - - { - const uint32 name_len = strlen(name); - - char* name_buffer = FX_SCRIPT_ALLOC_MEMORY(char, (name_len + 1)); - std::strcpy(name_buffer, name); - - name_token->Start = name_buffer; - name_token->Type = TT::Identifier; - name_token->Start[name_len] = 0; - name_token->Length = name_len + 1; - } - - FxScriptScope* definition_scope = &mScopes[0]; - - FxScriptVar var(type_token, name_token, definition_scope, true); - var.Value = value; - - definition_scope->Vars.Insert(var); - - // To prevent the variable data from being deleted here. - var.Name = nullptr; - var.Type = nullptr; -} - -FxAstNode* FxConfigScript::ParseStatementAsCommand(FxAstBlock* parent_block) -{ - if (mHasErrors) { - return nullptr; - } - - RETURN_IF_NO_TOKENS(nullptr); - - // Eat any extraneous semicolons - while (GetToken().Type == TT::Semicolon) { - EatToken(TT::Semicolon); - if (mTokenIndex >= mTokens.Size()) { - return nullptr; - } - } - - // Try to parse as a keyword first - FxAstNode* node = TryParseKeyword(parent_block); - - if (node) { - RETURN_IF_NO_TOKENS(node); - - EatToken(TT::Semicolon); - return node; - } - - // Check identifier - if (mTokenIndex < mTokens.Size() && GetToken().Type == TT::Identifier) { - TT next_token_type = TT::Unknown; - - if (mTokenIndex + 1 < mTokens.Size()) { - next_token_type = GetToken(1).Type; - } - - if (mTokenIndex + 1 < mTokens.Size() && GetToken(1).Type == TT::Equals) { - Token& assign_name = EatToken(TT::Identifier); - node = TryParseAssignment(&assign_name); - } - // If there is what looks to be a function call, try it - else { - FxScriptExternalFunc* external_action = FindExternalAction(GetToken().GetHash()); - if (external_action != nullptr) { - node = ParseActionCall(); - } - else { - FxScriptAction* action = FindAction(GetToken().GetHash()); - if (action != nullptr) { - node = ParseActionCall(); - } - } - } - // If there is just a semicolon after an identifier, parse as an rhs to print out later - if (!node && next_token_type == TT::Semicolon) { - node = ParseRhs(); - } - } - - RETURN_IF_NO_TOKENS(node); - - EatToken(TT::Semicolon); - - return node; -} - -FxAstNode* FxConfigScript::ParseStatement(FxAstBlock* parent_block) -{ - if (mHasErrors) { - return nullptr; - } - - RETURN_IF_NO_TOKENS(nullptr); - - if (GetToken().Type == TT::Dollar) { - EatToken(TT::Dollar); - - mInCommandMode = true; - - FxAstCommandMode* cmd_mode = FX_SCRIPT_ALLOC_NODE(FxAstCommandMode); - cmd_mode->Node = ParseStatementAsCommand(parent_block); - - mInCommandMode = false; - - return cmd_mode; - } - - while (GetToken().Type == TT::DocComment) { - FxAstDocComment* comment = FX_SCRIPT_ALLOC_NODE(FxAstDocComment); - comment->Comment = &EatToken(TT::DocComment); - CurrentDocComments.push_back(comment); - - if (mTokenIndex >= mTokens.Size()) { - return nullptr; - } - } - - // Eat any extraneous semicolons - while (GetToken().Type == TT::Semicolon) { - EatToken(TT::Semicolon); - - if (mTokenIndex >= mTokens.Size()) { - return nullptr; - } - } - - FxAstNode* node = TryParseKeyword(parent_block); - - if (!node && (mTokenIndex < mTokens.Size() && GetToken().Type == TT::Identifier)) { - if (mTokenIndex + 1 < mTokens.Size() && GetToken(1).Type == TT::LParen) { - node = ParseActionCall(); - } - else if (mTokenIndex + 1 < mTokens.Size() && GetToken(1).Type == TT::Equals) { - Token& assign_name = EatToken(TT::Identifier); - node = TryParseAssignment(&assign_name); - } - else { - GetToken().Print(); - } - } - - - if (!node) { - return nullptr; - } - - // Blocks do not require semicolons - if (node->NodeType == FX_AST_BLOCK || node->NodeType == FX_AST_ACTIONDECL) { - return node; - } - - EatToken(TT::Semicolon); - - return node; -} - -FxAstBlock* FxConfigScript::ParseBlock() -{ - bool in_command = mInCommandMode; - mInCommandMode = false; - - FxAstBlock* block = FX_SCRIPT_ALLOC_NODE(FxAstBlock); - - EatToken(TT::LBrace); - - while (GetToken().Type != TT::RBrace) { - FxAstNode* command = ParseStatement(block); - if (command == nullptr) { - break; - } - block->Statements.push_back(command); - } - - EatToken(TT::RBrace); - - mInCommandMode = in_command; - return block; -} - -FxAstActionDecl* FxConfigScript::ParseActionDeclare() -{ - FxAstActionDecl* node = FX_SCRIPT_ALLOC_NODE(FxAstActionDecl); - - if (!CurrentDocComments.empty()) { - node->DocComments = CurrentDocComments; - CurrentDocComments.clear(); - } - - // Name of the action - Token& name = EatToken(TT::Identifier); - - node->Name = &name; - - PushScope(); - EatToken(TT::LParen); - - FxAstBlock* params = FX_SCRIPT_ALLOC_NODE(FxAstBlock); - - // Parse the parameter list - while (GetToken().Type != TT::RParen) { - params->Statements.push_back(ParseVarDeclare()); - - if (GetToken().Type == TT::Comma) { - EatToken(TT::Comma); - continue; - } - - break; - } - - EatToken(TT::RParen); - - // Parse the return type - /*if (GetToken().Type != TT::LBrace) { - FxAstVarDecl* return_decl = ParseVarDeclare(); - node->ReturnVar = return_decl; - }*/ - - // Check to see if there is a return type provided - if (GetToken().Type != TT::LBrace) { - // There is a return type, declare the __ReturnVal__ variable - - // Get the token for the type - Token& type_token = EatToken(TT::Identifier); - - FxAstVarDecl* return_decl = InternalVarDeclare(mTokenReturnVar, &type_token); - node->ReturnVar = return_decl; - } - - - node->Block = ParseBlock(); - PopScope(); - - node->Params = params; - - FxScriptAction action(&name, mCurrentScope, node->Block, node); - mCurrentScope->Actions.Insert(action); - - return node; -} - -//FxScriptValue FxConfigScript::TryCallInternalFunc(FxHash func_name, std::vector& params) -//{ -// FxScriptValue return_value; -// -// for (const FxScriptInternalFunc& func : mInternalFuncs) { -// if (func.HashedName == func_name) { -// func.Func(params, &return_value); -// return return_value; -// } -// } -// -// return return_value; -//} - -FxAstActionCall* FxConfigScript::ParseActionCall() -{ - FxAstActionCall* node = FX_SCRIPT_ALLOC_NODE(FxAstActionCall); - - Token& name = EatToken(TT::Identifier); - - node->HashedName = name.GetHash(); - node->Action = FindAction(node->HashedName); - - TT end_token_type = TT::Semicolon; - - if (!mInCommandMode) { - end_token_type = TT::RParen; - } - - if (!mInCommandMode || GetToken().Type == TT::LParen) { - EatToken(TT::LParen); - } - - while (GetToken().Type != end_token_type) { - FxAstNode* param = ParseRhs(); - - if (param == nullptr) { - break; - } - - node->Params.push_back(param); - - TT next_tt = GetToken().Type; - - if (GetToken().Type == TT::Comma) { - EatToken(TT::Comma); - continue; - } - - if (mInCommandMode && (next_tt != TT::RParen && next_tt != TT::Semicolon)) { - continue; - } - - - break; - } - - if (!mInCommandMode || GetToken().Type == TT::RParen) { - EatToken(TT::RParen); - } - - return node; -} - - - -//void FxConfigScript::ParseDoCall() -//{ -// Token& call_name = EatToken(TT::Identifier); -// -// EatToken(TT::LParen); -// -// std::vector params; -// -// while (true) { -// params.push_back(ParseValue()); -// -// if (GetToken().Type == TT::Comma) { -// EatToken(TT::Comma); -// continue; -// } -// -// break; -// } -// -// EatToken(TT::RParen); -// -// TryCallInternalFunc(call_name.GetHash(), params); -// -// printf("Calling "); -// call_name.Print(); -// -// for (const auto& param : params) { -// printf("param : "); -// param.Print(); -// } -//} - - -FxAstBlock* FxConfigScript::Parse() -{ - FxAstBlock* root_block = FX_SCRIPT_ALLOC_NODE(FxAstBlock); - - FxAstNode* keyword; - while ((keyword = ParseStatement(root_block))) { - root_block->Statements.push_back(keyword); - } - - /*for (const auto& var : mCurrentScope->Vars) { - var.Print(); - }*/ - - if (mHasErrors) { - return nullptr; - } - - FxAstPrinter printer(root_block); - printer.Print(root_block); - - return root_block; -} - - - - -////////////////////////////////////////// -// Script Interpreter -////////////////////////////////////////// -//#if 0 -void FxScriptInterpreter::Create(FxAstBlock* root_block) -{ - mRootBlock = root_block; - - mScopes.Create(8); - mCurrentScope = mScopes.Insert(); - mCurrentScope->Parent = nullptr; - mCurrentScope->Vars.Create(FX_SCRIPT_SCOPE_LOCAL_VARS_START_SIZE); - mCurrentScope->Actions.Create(FX_SCRIPT_SCOPE_LOCAL_ACTIONS_START_SIZE); -} - - -void FxScriptInterpreter::Interpret() -{ - Visit(mRootBlock); - - mScopes[0].PrintAllVarsInScope(); -} - -void FxScriptInterpreter::PushScope() -{ - FxScriptScope* current = mCurrentScope; - - FxScriptScope* new_scope = mScopes.Insert(); - new_scope->Parent = current; - new_scope->Vars.Create(FX_SCRIPT_SCOPE_LOCAL_VARS_START_SIZE); - new_scope->Actions.Create(FX_SCRIPT_SCOPE_LOCAL_ACTIONS_START_SIZE); - - mCurrentScope = new_scope; -} - -void FxScriptInterpreter::PopScope() -{ - FxScriptScope* new_scope = mCurrentScope->Parent; - mScopes.RemoveLast(); - - assert(new_scope == &mScopes.GetLast()); - - mCurrentScope = new_scope; -} - -FxScriptVar* FxScriptInterpreter::FindVar(FxHash hashed_name) -{ - FxScriptScope* scope = mCurrentScope; - - while (scope) { - FxScriptVar* var = scope->FindVarInScope(hashed_name); - if (var) { - return var; - } - - scope = scope->Parent; - } - - return nullptr; -} - -FxScriptAction* FxScriptInterpreter::FindAction(FxHash hashed_name) -{ - FxScriptScope* scope = mCurrentScope; - - while (scope) { - FxScriptAction* var = scope->FindActionInScope(hashed_name); - if (var) { - return var; - } - - scope = scope->Parent; - } - - return nullptr; -} - -FxScriptExternalFunc* FxScriptInterpreter::FindExternalAction(FxHash hashed_name) -{ - for (FxScriptExternalFunc& func : mExternalFuncs) { - if (func.HashedName == hashed_name) { - return &func; - } - } - return nullptr; -} - -bool FxScriptInterpreter::CheckExternalCallArgs(FxAstActionCall* call, FxScriptExternalFunc& func) -{ - if (func.IsVariadic) { - return true; - } - - if (call->Params.size() != func.ParameterTypes.size()) { - return false; - } - - for (int i = 0; i < call->Params.size(); i++) { - FxScriptValue val = VisitRhs(call->Params[i]); - - if (!(val.Type & func.ParameterTypes[i])) { - return false; - } - } - - return true; -} - -FxScriptValue FxScriptInterpreter::VisitExternalCall(FxAstActionCall* call, FxScriptExternalFunc& func) -{ - FxScriptValue return_value; - - //PushScope(); - - if (!CheckExternalCallArgs(call, func)) { - printf("!!! Parameters do not match for function call!\n"); - PopScope(); - return return_value; - } - - std::vector params; - params.reserve(call->Params.size()); - - for (FxAstNode* param_node : call->Params) { - params.push_back(VisitRhs(param_node)); - } - - // func.Function(*this, params, &return_value); - - //PopScope(); - - return return_value; -} - -FxScriptValue FxScriptInterpreter::VisitActionCall(FxAstActionCall* call) -{ - FxScriptValue return_value; - - // This is not a local call, check for an internal function - if (call->Action == nullptr) { - for (FxScriptExternalFunc& func : mExternalFuncs) { - if (func.HashedName != call->HashedName) { - continue; - } - - return VisitExternalCall(call, func); - } - } - - if (call->Action == nullptr) { - puts("!!! Could not find action!"); - return return_value; - } - - PushScope(); - - std::vector param_decls = call->Action->Declaration->Params->Statements; - - if (call->Params.size() != param_decls.size()) { - printf("!!! MISMATCHED PARAM COUNTS\n"); - PopScope(); - - return return_value; - } - - // Assign each passed in value to the each parameter declaration - for (int i = 0; i < param_decls.size(); i++) { - FxAstVarDecl* decl = reinterpret_cast(param_decls[i]); - - FxScriptVar param(decl->Type, decl->Name, mCurrentScope); - param.Value = GetImmediateValue(VisitRhs(call->Params[i])); - - mCurrentScope->Vars.Insert(param); - } - - - if (call->Action->Declaration->ReturnVar) { - FxAstVarDecl* decl = call->Action->Declaration->ReturnVar; - - FxScriptVar return_var(decl->Type, decl->Name, mCurrentScope); - mCurrentScope->Vars.Insert(return_var); - - mCurrentScope->ReturnVar = &mCurrentScope->Vars.GetLast(); - - } - - Visit(call->Action->Block); - - // Acquire the return value from the scope - if (mCurrentScope->ReturnVar) { - return_value = GetImmediateValue(mCurrentScope->ReturnVar->Value); - } - - //mCurrentScope->PrintAllVarsInScope(); - - PopScope(); - - return return_value; -} - -FxScriptValue FxScriptInterpreter::VisitRhs(FxAstNode* node) -{ - if (node->NodeType == FX_AST_LITERAL) { - FxAstLiteral* literal = reinterpret_cast(node); - return literal->Value; - } - else if (node->NodeType == FX_AST_ACTIONCALL) { - FxAstActionCall* call = reinterpret_cast(node); - return VisitActionCall(call); - } - else if (node->NodeType == FX_AST_BINOP) { - FxAstBinop* binop = reinterpret_cast(node); - - FxScriptValue lhs_pre_val = VisitRhs(binop->Left); - FxScriptValue rhs_pre_val = VisitRhs(binop->Right); - - FxScriptValue lhs = GetImmediateValue(lhs_pre_val); - FxScriptValue rhs = GetImmediateValue(rhs_pre_val); - - float sign = (binop->OpToken->Type == TT::Plus) ? 1.0f : -1.0f; - - FxScriptValue result; - - if (lhs.Type == FxScriptValue::INT) { - result.ValueInt = lhs.ValueInt; - result.Type = FxScriptValue::INT; - - if (rhs.Type == FxScriptValue::INT) { - result.ValueInt += rhs.ValueInt * sign; - } - else if (rhs.Type == FxScriptValue::FLOAT) { - result.ValueInt += rhs.ValueFloat * sign; - } - } - else if (lhs.Type == FxScriptValue::FLOAT) { - result.ValueFloat = lhs.ValueFloat; - result.Type = FxScriptValue::FLOAT; - - if (rhs.Type == FxScriptValue::INT) { - result.ValueFloat += rhs.ValueInt * sign; - } - else if (rhs.Type == FxScriptValue::FLOAT) { - result.ValueFloat += rhs.ValueFloat * sign; - } - } - - return result; - } - - FxScriptValue value{}; - return value; -} - -void FxConfigScript::DefineDefaultExternalFunctions() -{ - // log([int | float | string | ref] args...) - RegisterExternalFunc( - FxHashStr("log"), - {}, // Do not check argument types as we handle it here - [](FxScriptVM* vm, std::vector& args, FxScriptValue* return_value) - { - printf("[SCRIPT]: "); - - for (int i = args.size() - 1; i >= 0; i--) { - - FxScriptValue& arg = args[i]; - - //const FxScriptValue& value = interpreter.GetImmediateValue(arg); - const FxScriptValue& value = arg; - - switch (value.Type) { - case FxScriptValue::NONETYPE: - printf("[none]"); - break; - case FxScriptValue::INT: - printf("%d", value.ValueInt); - break; - case FxScriptValue::FLOAT: - printf("%f", value.ValueFloat); - break; - case FxScriptValue::STRING: - printf("%s", value.ValueString); - break; - default: - printf("Unknown type\n"); - break; - } - - putchar(' '); - } - - putchar('\n'); - }, - true // Is variadic? - ); - - // listvars() - //RegisterExternalFunc( - // FxHashStr("__listvars__"), - // {}, - // [](FxScriptVM* vm, std::vector& args, FxScriptValue* return_value) - // { - // FxScriptScope* scope = interpreter.mCurrentScope; - // // Since there is a new scope created on function call, we need to start from the parent scope - // if (scope && scope->Parent) { - // scope = scope->Parent; - // } - - // while (scope != nullptr) { - // scope->PrintAllVarsInScope(); - // scope = scope->Parent; - // } - // }, - // false - //); - - // listactions() - //RegisterExternalFunc( - // FxHashStr("__listactions__"), - // {}, - // [](FxScriptVM* vm, std::vector& args, FxScriptValue* return_value) - // { - // FxScriptScope* scope = interpreter.mCurrentScope; - // // Since there is a new scope created on function call, we need to start from the parent scope - // if (scope && scope->Parent) { - // scope = scope->Parent; - // } - - // while (scope != nullptr) { - - // for (const FxScriptAction& action : scope->Actions) { - // // Print out the action name - // printf("action %.*s", action.Declaration->Name->Length, action.Declaration->Name->Start); - - // // Retrieve the declarations for all parameters - // std::vector& param_decls = action.Declaration->Params->Statements; - // const size_t param_count = param_decls.size(); - - // // Print out the parameter list - // putchar('('); - - // for (int i = 0; i < param_count; i++) { - // FxAstNode* param_node = param_decls[i]; - // if (param_node->NodeType != FX_AST_VARDECL) { - // continue; - // } - - // FxAstVarDecl* param_decl = reinterpret_cast(param_node); - - // // Print the type of the parameter - // printf("%.*s ", param_decl->Type->Length, param_decl->Type->Start); - - // // Print the name of the parameter - // printf("%.*s", param_decl->Name->Length, param_decl->Name->Start); - - // // If there are more parameters following, output a comma - // if (i < param_count - 1) { - // printf(", "); - // } - // } - - // putchar(')'); - - // // Print out the return type at the end of the declaration if it exists - // if (action.Declaration->ReturnVar) { - // printf(" %.*s", action.Declaration->ReturnVar->Type->Length, action.Declaration->ReturnVar->Type->Start); - // } - - // putchar('\n'); - // } - - // scope = scope->Parent; - // } - // }, - // false - //); -} - -void FxScriptInterpreter::VisitAssignment(FxAstAssign* assign) -{ - FxScriptVar* var = FindVar(assign->Var->Name->GetHash()); - - if (!var) { - printf("!!! Could not find variable!\n"); - return; - } - - constexpr FxHash builtin_int = FxHashStr("int"); - constexpr FxHash builtin_playerid = FxHashStr("playerid"); - constexpr FxHash builtin_float = FxHashStr("float"); - constexpr FxHash builtin_string = FxHashStr("string"); - - FxScriptValue rhs_value = VisitRhs(assign->Rhs); - const FxScriptValue& new_value = GetImmediateValue(rhs_value); - - FxScriptValue::ValueType var_type = FxScriptValue::NONETYPE; - - FxHash type_hash = var->Type->GetHash(); - - switch (type_hash) { - case builtin_playerid: - [[fallthrough]]; - case builtin_int: - var_type = FxScriptValue::INT; - break; - case builtin_float: - var_type = FxScriptValue::FLOAT; - break; - case builtin_string: - var_type = FxScriptValue::STRING; - break; - default: - printf("!!! Unknown type for variable %.*s!\n", var->Type->Length, var->Type->Start); - return; - } - - if (var_type != new_value.Type) { - printf("!!! Assignment value type does not match variable type!\n"); - return; - } - - - var->Value = new_value; - - //puts("Visit Assign"); -} - -void FxScriptInterpreter::Visit(FxAstNode* node) -{ - if (node == nullptr) { - return; - } - - if (node->NodeType == FX_AST_BLOCK) { - //puts("Visit Block"); - - FxAstBlock* block = reinterpret_cast(node); - for (FxAstNode* child : block->Statements) { - if (child->NodeType == FX_AST_RETURN) { - break; - } - - Visit(child); - } - } - else if (node->NodeType == FX_AST_ACTIONDECL) { - FxAstActionDecl* actiondecl = reinterpret_cast(node); - //puts("Visit ActionDecl"); - - FxScriptAction action(actiondecl->Name, mCurrentScope, actiondecl->Block, actiondecl); - mCurrentScope->Actions.Insert(action); - - //Visit(actiondecl->Block); - } - else if (node->NodeType == FX_AST_VARDECL) { - FxAstVarDecl* vardecl = reinterpret_cast(node); - //puts("Visit VarDecl"); - - FxScriptScope* scope = mCurrentScope; - if (vardecl->DefineAsGlobal) { - scope = &mScopes[0]; - } - - FxScriptVar var(vardecl->Type, vardecl->Name, scope); - scope->Vars.Insert(var); - - Visit(vardecl->Assignment); - } - else if (node->NodeType == FX_AST_ASSIGN) { - FxAstAssign* assign = reinterpret_cast(node); - VisitAssignment(assign); - } - else if (node->NodeType == FX_AST_ACTIONCALL) { - FxAstActionCall* actioncall = reinterpret_cast(node); - FxScriptValue return_value = VisitActionCall(actioncall); - - // If we are in command mode, print the result to the user - if (mInCommandMode && return_value.Type != FxScriptValue::NONETYPE) { - return_value.Print(); - } - } - // If we are in command mode, print the variable to the user - else if (node->NodeType == FX_AST_VARREF && mInCommandMode) { - FxAstVarRef* ref = reinterpret_cast(node); - FxScriptVar* var = FindVar(ref->Name->GetHash()); - - if (var) { - var->Print(); - } - } - // If we are in command mode, print the literal(probably result) to the user - else if (node->NodeType == FX_AST_LITERAL && mInCommandMode) { - FxAstLiteral* literal = reinterpret_cast(node); - GetImmediateValue(literal->Value).Print(); - } - // Executes a statement in command mode - else if (node->NodeType == FX_AST_COMMANDMODE) { - mInCommandMode = true; - Visit(reinterpret_cast(node)->Node); - mInCommandMode = false; - } - else { - puts("[UNKNOWN]"); - } -} - -void FxScriptInterpreter::DefineExternalVar(const char* type, const char* name, const FxScriptValue& value) -{ - Token* name_token = FX_SCRIPT_ALLOC_MEMORY(Token, sizeof(Token)); - Token* type_token = FX_SCRIPT_ALLOC_MEMORY(Token, sizeof(Token)); - - { - const uint32 type_len = strlen(type); - char* type_buffer = FX_SCRIPT_ALLOC_MEMORY(char, (type_len + 1)); - std::strcpy(type_buffer, type); - //type_buffer[type_len + 1] = 0; - - - type_token->Start = type_buffer; - type_token->Type = TT::Identifier; - type_token->Start[type_len] = 0; - type_token->Length = type_len + 1; - } - - { - const uint32 name_len = strlen(name); - - char* name_buffer = FX_SCRIPT_ALLOC_MEMORY(char, (name_len + 1)); - std::strcpy(name_buffer, name); - - name_token->Start = name_buffer; - name_token->Type = TT::Identifier; - name_token->Start[name_len] = 0; - name_token->Length = name_len + 1; - } - - FxScriptScope* definition_scope = &mScopes[0]; - - FxScriptVar var(type_token, name_token, definition_scope, true); - var.Value = value; - - definition_scope->Vars.Insert(var); -} - -const FxScriptValue& FxScriptInterpreter::GetImmediateValue(const FxScriptValue& value) -{ - // If the value is a reference, get the value of that reference - if (value.Type == FxScriptValue::REF) { - FxScriptVar* var = FindVar(value.ValueRef->Name->GetHash()); - - if (!var) { - printf("!!! Undefined reference to variable\n"); - //value.ValueRef->Name->Print(); - putchar('\n'); - - return FxScriptValue::None; - } - - return var->Value; - } - - return value; -} - -//#endif - -void FxConfigScript::RegisterExternalFunc(FxHash func_name, std::vector param_types, FxScriptExternalFunc::FuncType callback, bool is_variadic) -{ - FxScriptExternalFunc func{ - .HashedName = func_name, - .Function = callback, - .ParameterTypes = param_types, - .IsVariadic = is_variadic, - }; - - mExternalFuncs.push_back(func); -} - - -///////////////////////////////////////// -// Script Bytecode Emitter -///////////////////////////////////////// - -#include "FxScriptBytecode.hpp" - -void FxScriptBCEmitter::BeginEmitting(FxAstNode* node) -{ - mStackSize = 1024; - - /*mStack = new uint8[1024]; - mStackStart = mStack;*/ - - mBytecode.Create(4096); - VarHandles.Create(64); - - Emit(node); - - printf("\n"); - - PrintBytecode(); -} - -#define RETURN_IF_NO_NODE(node_) \ - if ((node_) == nullptr) { return; } - -#define RETURN_VALUE_IF_NO_NODE(node_, value_) \ - if ((node_) == nullptr) { return (value_); } - -void FxScriptBCEmitter::Emit(FxAstNode* node) -{ - RETURN_IF_NO_NODE(node); - - if (node->NodeType == FX_AST_BLOCK) { - return EmitBlock(reinterpret_cast(node)); - } - else if (node->NodeType == FX_AST_ACTIONDECL) { - return EmitAction(reinterpret_cast(node)); - } - else if (node->NodeType == FX_AST_ACTIONCALL) { - return DoActionCall(reinterpret_cast(node)); - } - else if (node->NodeType == FX_AST_ASSIGN) { - return EmitAssign(reinterpret_cast(node)); - } - else if (node->NodeType == FX_AST_VARDECL) { - DoVarDeclare(reinterpret_cast(node)); - return; - } - else if (node->NodeType == FX_AST_RETURN) { - constexpr FxHash return_val_hash = FxHashStr(FX_SCRIPT_VAR_RETURN_VAL); - - FxScriptBytecodeVarHandle* return_var = FindVarHandle(return_val_hash); - - if (return_var) { - DoLoad(return_var->Offset, FX_REG_XR); - } - - EmitJumpReturnToCaller(); - - return; - } -} - -FxScriptBytecodeVarHandle* FxScriptBCEmitter::FindVarHandle(FxHash hashed_name) -{ - for (FxScriptBytecodeVarHandle& handle : VarHandles) { - if (handle.HashedName == hashed_name) { - return &handle; - } - } - return nullptr; -} - -FxScriptBytecodeActionHandle* FxScriptBCEmitter::FindActionHandle(FxHash hashed_name) -{ - for (FxScriptBytecodeActionHandle& handle : ActionHandles) { - if (handle.HashedName == hashed_name) { - return &handle; - } - } - return nullptr; -} - - - -FxScriptRegister FxScriptBCEmitter::FindFreeRegister() -{ - uint16 gp_r = 0x01; - - const uint16 num_gp_regs = FX_REG_X3; - - for (int i = 0; i < num_gp_regs; i++) { - if (!(mRegsInUse & gp_r)) { - // We are starting on 0x01, so register index should be N + 1 - const int register_index = i + 1; - - return static_cast(register_index); - } - - gp_r <<= 1; - } - - return FxScriptRegister::FX_REG_NONE; -} - -const char* FxScriptBCEmitter::GetRegisterName(FxScriptRegister reg) -{ - switch (reg) { - case FX_REG_NONE: - return "NONE"; - case FX_REG_X0: - return "X0"; - case FX_REG_X1: - return "X1"; - case FX_REG_X2: - return "X2"; - case FX_REG_X3: - return "X3"; - case FX_REG_RA: - return "RA"; - case FX_REG_XR: - return "XR"; - case FX_REG_SP: - return "SP"; - default:; - }; - - return "NONE"; -} - -FxScriptRegister FxScriptBCEmitter::RegFlagToReg(FxScriptRegisterFlag reg_flag) -{ - switch (reg_flag) { - case FX_REGFLAG_NONE: - return FX_REG_NONE; - case FX_REGFLAG_X0: - return FX_REG_X0; - case FX_REGFLAG_X1: - return FX_REG_X1; - case FX_REGFLAG_X2: - return FX_REG_X2; - case FX_REGFLAG_X3: - return FX_REG_X3; - case FX_REGFLAG_RA: - return FX_REG_RA; - case FX_REGFLAG_XR: - return FX_REG_XR; - } - - return FX_REG_NONE; -} - -FxScriptRegisterFlag FxScriptBCEmitter::RegToRegFlag(FxScriptRegister reg) -{ - switch (reg) { - case FX_REG_NONE: - return FX_REGFLAG_NONE; - case FX_REG_X0: - return FX_REGFLAG_X0; - case FX_REG_X1: - return FX_REGFLAG_X1; - case FX_REG_X2: - return FX_REGFLAG_X2; - case FX_REG_X3: - return FX_REGFLAG_X3; - case FX_REG_RA: - return FX_REGFLAG_RA; - case FX_REG_XR: - return FX_REGFLAG_XR; - case FX_REG_SP: - return FX_REGFLAG_NONE; - default:; - } - - return FX_REGFLAG_NONE; -} - - -void FxScriptBCEmitter::Write16(uint16 value) -{ - mBytecode.Insert(static_cast(value >> 8)); - mBytecode.Insert(static_cast(value)); -} - -void FxScriptBCEmitter::Write32(uint32 value) -{ - Write16(static_cast(value >> 16)); - Write16(static_cast(value)); -} - -void FxScriptBCEmitter::WriteOp(uint8 op_base, uint8 op_spec) -{ - mBytecode.Insert(op_base); - mBytecode.Insert(op_spec); -} - -using RhsMode = FxScriptBCEmitter::RhsMode; - -#define MARK_REGISTER_USED(regn_) { MarkRegisterUsed(regn_); } -#define MARK_REGISTER_FREE(regn_) { MarkRegisterFree(regn_); } - -void FxScriptBCEmitter::MarkRegisterUsed(FxScriptRegister reg) -{ - FxScriptRegisterFlag rflag = RegToRegFlag(reg); - mRegsInUse = static_cast(uint16(mRegsInUse) | uint16(rflag)); -} - -void FxScriptBCEmitter::MarkRegisterFree(FxScriptRegister reg) -{ - FxScriptRegisterFlag rflag = RegToRegFlag(reg); - - mRegsInUse = static_cast(uint16(mRegsInUse) & (~uint16(rflag))); -} - -void FxScriptBCEmitter::EmitSave32(int16 offset, uint32 value) -{ - // SAVE32 [i16 offset] [i32] - WriteOp(OpBase_Save, OpSpecSave_Int32); - - Write16(offset); - Write32(value); -} - -void FxScriptBCEmitter::EmitSaveReg32(int16 offset, FxScriptRegister reg) -{ - // SAVE32r [i16 offset] [%r32] - WriteOp(OpBase_Save, OpSpecSave_Reg32); - - Write16(offset); - Write16(reg); -} - - -void FxScriptBCEmitter::EmitSaveAbsolute32(uint32 position, uint32 value) -{ - // SAVE32a [i32 offset] [i32] - WriteOp(OpBase_Save, OpSpecSave_AbsoluteInt32); - - Write32(position); - Write32(value); -} - -void FxScriptBCEmitter::EmitSaveAbsoluteReg32(uint32 position, FxScriptRegister reg) -{ - // SAVE32r [i32 offset] [%r32] - WriteOp(OpBase_Save, OpSpecSave_AbsoluteReg32); - - Write32(position); - Write16(reg); -} - -void FxScriptBCEmitter::EmitPush32(uint32 value) -{ - // PUSH32 [i32] - WriteOp(OpBase_Push, OpSpecPush_Int32); - Write32(value); - - mStackOffset += 4; -} - -void FxScriptBCEmitter::EmitPush32r(FxScriptRegister reg) -{ - // PUSH32r [%r32] - WriteOp(OpBase_Push, OpSpecPush_Reg32); - Write16(reg); - - mStackOffset += 4; -} - - -void FxScriptBCEmitter::EmitPop32(FxScriptRegister output_reg) -{ - // POP32 [%r32] - WriteOp(OpBase_Pop, (OpSpecPop_Int32 << 4) | (output_reg & 0x0F)); - - mStackOffset -= 4; -} - -void FxScriptBCEmitter::EmitLoad32(int offset, FxScriptRegister output_reg) -{ - // LOAD [i16] [%r32] - WriteOp(OpBase_Load, (OpSpecLoad_Int32 << 4) | (output_reg & 0x0F)); - Write16(static_cast(offset)); -} - -void FxScriptBCEmitter::EmitLoadAbsolute32(uint32 position, FxScriptRegister output_reg) -{ - // LOADA [i32] [%r32] - WriteOp(OpBase_Load, (OpSpecLoad_AbsoluteInt32 << 4) | (output_reg & 0x0F)); - Write32(position); -} - -void FxScriptBCEmitter::EmitJumpRelative(uint16 offset) -{ - WriteOp(OpBase_Jump, OpSpecJump_Relative); - Write16(offset); -} - -void FxScriptBCEmitter::EmitJumpAbsolute(uint32 position) -{ - WriteOp(OpBase_Jump, OpSpecJump_Absolute); - Write32(position); -} - - -void FxScriptBCEmitter::EmitJumpAbsoluteReg32(FxScriptRegister reg) -{ - WriteOp(OpBase_Jump, OpSpecJump_AbsoluteReg32); - Write16(reg); -} - -void FxScriptBCEmitter::EmitJumpCallAbsolute(uint32 position) -{ - WriteOp(OpBase_Jump, OpSpecJump_CallAbsolute); - Write32(position); -} - - -void FxScriptBCEmitter::EmitJumpCallExternal(FxHash hashed_name) -{ - WriteOp(OpBase_Jump, OpSpecJump_CallExternal); - Write32(hashed_name); -} - -void FxScriptBCEmitter::EmitJumpReturnToCaller() -{ - WriteOp(OpBase_Jump, OpSpecJump_ReturnToCaller); -} - -void FxScriptBCEmitter::EmitMoveInt32(FxScriptRegister reg, uint32 value) -{ - WriteOp(OpBase_Move, (OpSpecMove_Int32 << 4) | (reg & 0x0F)); - Write32(value); -} - -void FxScriptBCEmitter::EmitParamsStart() -{ - WriteOp(OpBase_Data, OpSpecData_ParamsStart); -} - -void FxScriptBCEmitter::EmitType(FxScriptValue::ValueType type) -{ - OpSpecType op_type = OpSpecType_Int; - - if (type == FxScriptValue::STRING) { - op_type = OpSpecType_String; - } - - WriteOp(OpBase_Type, op_type); -} - -uint32 FxScriptBCEmitter::EmitDataString(char* str, uint16 length) -{ - - WriteOp(OpBase_Data, OpSpecData_String); - - uint32 start_index = mBytecode.Size(); - - uint16 final_length = length + 1; - - // If the length is not a factor of 2 (sizeof uint16) then add a byte of padding - if ((final_length & 0x01)) { - ++final_length; - } - - Write16(final_length); - - for (int i = 0; i < final_length; i += 2) { - mBytecode.Insert(str[i]); - - if (i >= length) { - mBytecode.Insert(0); - break; - } - - mBytecode.Insert(str[i + 1]); - } - - return start_index; -} - - -FxScriptRegister FxScriptBCEmitter::EmitBinop(FxAstBinop* binop, FxScriptBytecodeVarHandle* handle) -{ - bool will_preserve_lhs = false; - // Load the A and B values into the registers - FxScriptRegister a_reg = EmitRhs(binop->Left, RhsMode::RHS_FETCH_TO_REGISTER, handle); - - // Since there is a chance that this register will be clobbered (by binop, action call, etc), we will - // push the value of the register here and return it after processing the RHS - if (binop->Right->NodeType != FX_AST_LITERAL) { - will_preserve_lhs = true; - EmitPush32r(a_reg); - } - - FxScriptRegister b_reg = EmitRhs(binop->Right, RhsMode::RHS_FETCH_TO_REGISTER, handle); - - // Retrieve the previous LHS - if (will_preserve_lhs) { - EmitPop32(a_reg); - } - - if (binop->OpToken->Type == TT::Plus) { - WriteOp(OpBase_Arith, OpSpecArith_Add); - - mBytecode.Insert(a_reg); - mBytecode.Insert(b_reg); - } - - // We no longer need the lhs or rhs registers, free em - MARK_REGISTER_FREE(a_reg); - MARK_REGISTER_FREE(b_reg); - - return FX_REG_XR; -} - -FxScriptRegister FxScriptBCEmitter::EmitVarFetch(FxAstVarRef* ref, RhsMode mode) -{ - FxScriptBytecodeVarHandle* var_handle = FindVarHandle(ref->Name->GetHash()); - - bool force_absolute_load = false; - - // If the variable is from a previous scope, load it from an absolute address. local offsets - // will change depending on where they are called. - if (var_handle->ScopeIndex < mScopeIndex) { - force_absolute_load = true; - } - - if (!var_handle) { - printf("Could not find var handle!"); - return FX_REG_NONE; - } - - FxScriptRegister reg = FindFreeRegister(); - - MARK_REGISTER_USED(reg); - - DoLoad(var_handle->Offset, reg, force_absolute_load); - - if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { - return reg; - } - - // If we are just copying the variable to this new variable, we can free the register after - // we push to the stack. - if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { - if (var_handle->Type == FxScriptValue::STRING) { - EmitType(var_handle->Type); - } - - EmitPush32r(reg); - MARK_REGISTER_FREE(reg); - - return FX_REG_NONE; - } - - // This is a reference to a variable, return the register we loaded it into - return reg; -} - -void FxScriptBCEmitter::DoLoad(uint32 stack_offset, FxScriptRegister output_reg, bool force_absolute) -{ - if (stack_offset < 0xFFFE && !force_absolute) { - // Relative load - - // Calculate the relative index to the current stack offset - int input_offset = -(static_cast(mStackOffset) - static_cast(stack_offset)); - - EmitLoad32(input_offset, output_reg); - } - else { - // Absolute load - EmitLoadAbsolute32(stack_offset, output_reg); - } -} - -void FxScriptBCEmitter::DoSaveInt32(uint32 stack_offset, uint32 value, bool force_absolute) -{ - if (stack_offset < 0xFFFE && !force_absolute) { - // Relative save - - // Calculate the relative index to the current stack offset - int input_offset = -(static_cast(mStackOffset) - static_cast(stack_offset)); - - EmitSave32(input_offset, value); - } - else { - // Absolute save - EmitSaveAbsolute32(stack_offset, value); - } -} - -void FxScriptBCEmitter::DoSaveReg32(uint32 stack_offset, FxScriptRegister reg, bool force_absolute) -{ - if (stack_offset < 0xFFFE && !force_absolute) { - // Relative save - - // Calculate the relative index to the current stack offset - int input_offset = -(static_cast(mStackOffset) - static_cast(stack_offset)); - - EmitSaveReg32(input_offset, reg); - } - else { - // Absolute save - EmitSaveAbsoluteReg32(stack_offset, reg); - } -} - -void FxScriptBCEmitter::EmitAssign(FxAstAssign* assign) -{ - FxScriptBytecodeVarHandle* var_handle = FindVarHandle(assign->Var->Name->GetHash()); - if (var_handle == nullptr) { - printf("!!! Var '%.*s' does not exist!\n", assign->Var->Name->Length, assign->Var->Name->Start); - return; - } - - // bool force_absolute_save = false; - - // if (var_handle->ScopeIndex < mScopeIndex) { - // force_absolute_save = true; - // } - - //int output_offset = -(static_cast(mStackOffset) - static_cast(var_handle->Offset)); - - if (!var_handle) { - printf("Could not find var handle to assign to!"); - return; - } - - EmitRhs(assign->Rhs, RhsMode::RHS_ASSIGN_TO_HANDLE, var_handle); -} - -FxScriptRegister FxScriptBCEmitter::EmitLiteralInt(FxAstLiteral* literal, RhsMode mode, FxScriptBytecodeVarHandle* handle) -{ - // If this is on variable definition, push the value to the stack. - if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { - EmitPush32(literal->Value.ValueInt); - - return FX_REG_NONE; - } - - // If this is as a literal, push the value to the stack and pop onto the target register. - else if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { - //EmitPush32(literal->Value.ValueInt); - - FxScriptRegister output_reg = FindFreeRegister(); - //EmitPop32(output_reg); - - EmitMoveInt32(output_reg, literal->Value.ValueInt); - - // Mark the output register as used to store it - MARK_REGISTER_USED(output_reg); - - return output_reg; - } - - else if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { - const bool force_absolute_save = (handle->ScopeIndex < mScopeIndex); - DoSaveInt32(handle->Offset, literal->Value.ValueInt, force_absolute_save); - - return FX_REG_NONE; - } - - return FX_REG_NONE; -} - - -FxScriptRegister FxScriptBCEmitter::EmitLiteralString(FxAstLiteral* literal, RhsMode mode, FxScriptBytecodeVarHandle* handle) -{ - const uint32 string_length = strlen(literal->Value.ValueString); - - // Emit the length and string data - const uint32 string_position = EmitDataString(literal->Value.ValueString, string_length); - - // local string some_value = "Some String"; - if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { - // Push the location and mark it as a pointer to a string - EmitType(FxScriptValue::STRING); - EmitPush32(string_position); - - return FX_REG_NONE; - } - - // some_function("Some String") - else if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { - // Push the location for the string and pop it back to a register. - EmitType(FxScriptValue::STRING); - - // Push the string position - //EmitPush32(string_position); - - // Find a register to output to and write the index - FxScriptRegister output_reg = FindFreeRegister(); - //EmitPop32(output_reg); - - EmitMoveInt32(output_reg, string_position); - - // Mark the output register as used to store it - MARK_REGISTER_USED(output_reg); - - return output_reg; - } - - // some_previous_value = "Some String"; - else if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { - const bool force_absolute_save = (handle->ScopeIndex < mScopeIndex); - - DoSaveInt32(handle->Offset, string_position, force_absolute_save); - handle->Type = FxScriptValue::STRING; - - return FX_REG_NONE; - } - - return FX_REG_NONE; -} - -FxScriptRegister FxScriptBCEmitter::EmitRhs(FxAstNode* rhs, FxScriptBCEmitter::RhsMode mode, FxScriptBytecodeVarHandle* handle) -{ - if (rhs->NodeType == FX_AST_LITERAL) { - FxAstLiteral* literal = reinterpret_cast(rhs); - - if (literal->Value.Type == FxScriptValue::INT) { - return EmitLiteralInt(literal, mode, handle); - } - else if (literal->Value.Type == FxScriptValue::STRING) { - return EmitLiteralString(literal, mode, handle); - } - else if (literal->Value.Type == FxScriptValue::REF) { - // Reference another value, load from memory into register - FxScriptRegister output_register = EmitVarFetch(literal->Value.ValueRef, mode); - if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { - DoSaveReg32(handle->Offset, output_register); - } - - return output_register; - } - - return FX_REG_NONE; - } - - else if (rhs->NodeType == FX_AST_ACTIONCALL || rhs->NodeType == FX_AST_BINOP) { - FxScriptRegister result_register = FX_REG_XR; - - if (rhs->NodeType == FX_AST_BINOP) { - result_register = EmitBinop(reinterpret_cast(rhs), handle); - } - - else if (rhs->NodeType == FX_AST_ACTIONCALL) { - DoActionCall(reinterpret_cast(rhs)); - // Action results are stored in XR - result_register = FX_REG_XR; - } - - - if (mode == RhsMode::RHS_DEFINE_IN_MEMORY) { - uint32 offset = mStackOffset; - EmitPush32r(result_register); - - if (handle) { - handle->Offset = offset; - } - - return FX_REG_NONE; - } - - else if (mode == RhsMode::RHS_FETCH_TO_REGISTER) { - // Push the result to a register - EmitPush32r(result_register); - - // Find a register to output to, and pop the value to there. - FxScriptRegister output_reg = FindFreeRegister(); - EmitPop32(output_reg); - - // Mark the output register as used to store it - MARK_REGISTER_USED(output_reg); - - return output_reg; - } - else if (mode == RhsMode::RHS_ASSIGN_TO_HANDLE) { - const bool force_absolute_save = (handle->ScopeIndex < mScopeIndex); - - // Save the value back to the variable - DoSaveReg32(handle->Offset, result_register, force_absolute_save); - - return FX_REG_NONE; - } - } - - return FX_REG_NONE; -} - -FxScriptBytecodeVarHandle* FxScriptBCEmitter::DoVarDeclare(FxAstVarDecl* decl, VarDeclareMode mode) -{ - RETURN_VALUE_IF_NO_NODE(decl, nullptr); - - const uint16 size_of_type = static_cast(sizeof(int32)); - - const FxHash type_int = FxHashStr("int"); - const FxHash type_string = FxHashStr("string"); - - FxHash decl_hash = decl->Name->GetHash(); - - FxScriptValue::ValueType value_type = FxScriptValue::INT; - - switch (decl_hash) { - case type_int: - value_type = FxScriptValue::INT; - break; - case type_string: - value_type = FxScriptValue::STRING; - break; - }; - - FxScriptBytecodeVarHandle handle{ - .HashedName = decl->Name->GetHash(), - .Type = value_type, // Just int for now - .Offset = (mStackOffset), - .SizeOnStack = size_of_type, - .ScopeIndex = mScopeIndex, - }; - - VarHandles.Insert(handle); - - FxScriptBytecodeVarHandle* inserted_handle = &VarHandles[VarHandles.Size() - 1]; - - if (mode == DECLARE_NO_EMIT) { - // Do not emit any values - return inserted_handle; - } - - - if (decl->Assignment) { - FxAstNode* rhs = decl->Assignment->Rhs; - - EmitRhs(rhs, RhsMode::RHS_DEFINE_IN_MEMORY, inserted_handle); - - - // EmitPush32(0); - - // EmitRhs(rhs, RhsMode::RHS_ASSIGN_TO_HANDLE, inserted_handle); - } - else { - // There is no assignment, push zero as the value for now and - // a later assignment can set it using save32. - EmitPush32(0); - } - - return inserted_handle; -} - -void FxScriptBCEmitter::DoActionCall(FxAstActionCall* call) -{ - RETURN_IF_NO_NODE(call); - - FxScriptBytecodeActionHandle* handle = FindActionHandle(call->HashedName); - - - std::vector call_locations; - call_locations.reserve(8); - - // Push all params to stack - for (FxAstNode* param : call->Params) { - // FxScriptRegister reg = - if (param->NodeType == FX_AST_ACTIONCALL) { - EmitRhs(param, RhsMode::RHS_DEFINE_IN_MEMORY, nullptr); - call_locations.push_back(mStackOffset - 4); - } - //MARK_REGISTER_FREE(reg); - } - - EmitPush32r(FX_REG_RA); - - EmitParamsStart(); - - int call_location_index = 0; - - // Push all params to stack - for (FxAstNode* param : call->Params) { - if (param->NodeType == FX_AST_ACTIONCALL) { - FxScriptRegister temp_register = FindFreeRegister(); - - DoLoad(call_locations[call_location_index], temp_register); - call_location_index++; - - EmitPush32r(temp_register); - - continue; - } - - EmitRhs(param, RhsMode::RHS_DEFINE_IN_MEMORY, nullptr); - } - - // The handle could not be found, write it as a possible external symbol. - if (!handle) { - printf("Call name-> %u\n", call->HashedName); - - // Since popping the parameters are handled internally in the VM, - // we need to decrement the stack offset here. - for (int i = 0; i < call->Params.size(); i++) { - mStackOffset -= 4; - } - - EmitJumpCallExternal(call->HashedName); - - EmitPop32(FX_REG_RA); - return; - } - - EmitJumpCallAbsolute(handle->BytecodeIndex); - - EmitPop32(FX_REG_RA); -} - -FxScriptBytecodeVarHandle* FxScriptBCEmitter::DefineAndFetchParam(FxAstNode* param_decl_node) -{ - if (param_decl_node->NodeType != FX_AST_VARDECL) { - printf("!!! param node type is not vardecl!\n"); - return nullptr; - } - - // Emit variable without emitting pushes or pops - FxScriptBytecodeVarHandle* handle = DoVarDeclare(reinterpret_cast(param_decl_node), DECLARE_NO_EMIT); - - if (!handle) { - printf("!!! could not define and fetch param!\n"); - return nullptr; - } - - assert(handle->SizeOnStack == 4); - - mStackOffset += handle->SizeOnStack; - - return handle; -} - -FxScriptBytecodeVarHandle* FxScriptBCEmitter::DefineReturnVar(FxAstVarDecl* decl) -{ - RETURN_VALUE_IF_NO_NODE(decl, nullptr); - - return DoVarDeclare(decl); -} - -void FxScriptBCEmitter::EmitAction(FxAstActionDecl* action) -{ - RETURN_IF_NO_NODE(action); - - ++mScopeIndex; - - // Store the bytecode offset before the action is emitted - - const size_t start_of_action = mBytecode.Size(); - printf("Start of action %zu\n", start_of_action); - - // Emit the jump instruction, we will update the jump position after emitting all of the code inside the block - EmitJumpRelative(0); - - //const uint32 initial_stack_offset = mStackOffset; - - const size_t header_jump_start_index = start_of_action + sizeof(uint16); - - size_t start_var_handle_count = VarHandles.Size(); - - // Offset for the pushed return address - mStackOffset += 4; - - // Emit the body of the action - { - for (FxAstNode* param_decl_node : action->Params->Statements) { - DefineAndFetchParam(param_decl_node); - } - - FxScriptBytecodeVarHandle* return_var = DefineReturnVar(action->ReturnVar); - - EmitBlock(action->Block); - - // Check to see if there has been a return statement in the action - bool block_has_return = false; - - for (FxAstNode* statement : action->Block->Statements) { - if (statement->NodeType == FX_AST_RETURN) { - block_has_return = true; - break; - } - } - - // There is no return statement in the action's block, add a return statement - if (!block_has_return) { - EmitJumpReturnToCaller(); - - if (return_var != nullptr) { - DoLoad(return_var->Offset, FX_REG_XR); - } - } - } - - // Return offset back to pre-call - mStackOffset -= 4; - - const size_t end_of_action = mBytecode.Size(); - const uint16 distance_to_action = static_cast(end_of_action - (start_of_action)-4); - - // Update the jump to the end of the action - mBytecode[header_jump_start_index] = static_cast(distance_to_action >> 8); - mBytecode[header_jump_start_index + 1] = static_cast((distance_to_action & 0xFF)); - - FxScriptBytecodeActionHandle action_handle{ - .HashedName = action->Name->GetHash(), - .BytecodeIndex = static_cast(start_of_action + 4) - }; - - const size_t number_of_scope_var_handles = VarHandles.Size() - start_var_handle_count; - printf("Number of var handles to remove: %zu\n", number_of_scope_var_handles); - - ActionHandles.push_back(action_handle); - - --mScopeIndex; - - // Delete the variables on the stack - for (int i = 0; i < number_of_scope_var_handles; i++) { - FxScriptBytecodeVarHandle* var = VarHandles.RemoveLast(); - assert(var->SizeOnStack == 4); - mStackOffset -= var->SizeOnStack; - } -} - -void FxScriptBCEmitter::EmitBlock(FxAstBlock* block) -{ - RETURN_IF_NO_NODE(block); - - for (FxAstNode* node : block->Statements) { - Emit(node); - } -} - -void FxScriptBCEmitter::PrintBytecode() -{ - const size_t size = mBytecode.Size(); - for (int i = 0; i < 25; i++) { - printf("%02d ", i); - } - printf("\n"); - for (int i = 0; i < 25; i++) { - printf("---"); - } - printf("\n"); - - for (size_t i = 0; i < size; i++) { - printf("%02X ", mBytecode[i]); - - if (i > 0 && ((i + 1) % 25) == 0) { - printf("\n"); - } - } - printf("\n"); -} - - - -///////////////////////////////////// -// Bytecode Printer -///////////////////////////////////// - -uint16 FxScriptBCPrinter::Read16() -{ - uint8 lo = mBytecode[mBytecodeIndex++]; - uint8 hi = mBytecode[mBytecodeIndex++]; - - return ((static_cast(lo) << 8) | hi); -} - -uint32 FxScriptBCPrinter::Read32() -{ - uint16 lo = Read16(); - uint16 hi = Read16(); - - return ((static_cast(lo) << 16) | hi); -} - -#define BC_PRINT_OP(fmt_, ...) snprintf(s, 128, fmt_, ##__VA_ARGS__) - -void FxScriptBCPrinter::DoLoad(char* s, uint8 op_base, uint8 op_spec_raw) -{ - uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); - uint8 op_reg = (op_spec_raw & 0x0F); - - if (op_spec == OpSpecLoad_Int32) { - int16 offset = Read16(); - BC_PRINT_OP("load32 %d, %s", offset, FxScriptBCEmitter::GetRegisterName(static_cast(op_reg))); - } - else if (op_spec == OpSpecLoad_AbsoluteInt32) { - uint32 offset = Read32(); - BC_PRINT_OP("load32a %u, %s", offset, FxScriptBCEmitter::GetRegisterName(static_cast(op_reg))); - } -} - -void FxScriptBCPrinter::DoPush(char* s, uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecPush_Int32) { - uint32 value = Read32(); - BC_PRINT_OP("push32 %u", value); - } - else if (op_spec == OpSpecPush_Reg32) { - uint16 reg = Read16(); - BC_PRINT_OP("push32r %s", FxScriptBCEmitter::GetRegisterName(static_cast(reg))); - } -} - -void FxScriptBCPrinter::DoPop(char* s, uint8 op_base, uint8 op_spec_raw) -{ - uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); - uint8 op_reg = (op_spec_raw & 0x0F); - - if (op_spec == OpSpecPush_Int32) { - BC_PRINT_OP("pop32 %s", FxScriptBCEmitter::GetRegisterName(static_cast(op_reg))); - } -} - -void FxScriptBCPrinter::DoArith(char* s, uint8 op_base, uint8 op_spec) -{ - uint8 a_reg = mBytecode[mBytecodeIndex++]; - uint8 b_reg = mBytecode[mBytecodeIndex++]; - - if (op_spec == OpSpecArith_Add) { - BC_PRINT_OP("add32 %s, %s", FxScriptBCEmitter::GetRegisterName(static_cast(a_reg)), FxScriptBCEmitter::GetRegisterName(static_cast(b_reg))); - } -} - -void FxScriptBCPrinter::DoSave(char* s, uint8 op_base, uint8 op_spec) -{ - - // Save a imm32 into an offset in the stack - if (op_spec == OpSpecSave_Int32) { - const int16 offset = Read16(); - const uint32 value = Read32(); - - BC_PRINT_OP("save32 %d, %u", offset, value); - } - - // Save a register into an offset in the stack - else if (op_spec == OpSpecSave_Reg32) { - const int16 offset = Read16(); - uint16 reg = Read16(); - - BC_PRINT_OP("save32r %d, %s", offset, FxScriptBCEmitter::GetRegisterName(static_cast(reg))); - } - else if (op_spec == OpSpecSave_AbsoluteInt32) { - const uint32 offset = Read32(); - const uint32 value = Read32(); - - BC_PRINT_OP("save32a %u, %u", offset, value); - } - else if (op_spec == OpSpecSave_AbsoluteReg32) { - const uint32 offset = Read32(); - uint16 reg = Read16(); - - BC_PRINT_OP("save32ar %u, %s", offset, FxScriptBCEmitter::GetRegisterName(static_cast(reg))); - } -} - -void FxScriptBCPrinter::DoJump(char* s, uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecJump_Relative) { - uint16 offset = Read16(); - printf("# jump relative to (%u)\n", mBytecodeIndex + offset); - BC_PRINT_OP("jmpr %d", offset); - } - else if (op_spec == OpSpecJump_Absolute) { - uint32 position = Read32(); - BC_PRINT_OP("jmpa %u", position); - } - else if (op_spec == OpSpecJump_AbsoluteReg32) { - uint16 reg = Read16(); - BC_PRINT_OP("jmpar %s", FxScriptBCEmitter::GetRegisterName(static_cast(reg))); - } - else if (op_spec == OpSpecJump_CallAbsolute) { - uint32 position = Read32(); - BC_PRINT_OP("calla %u", position); - } - else if (op_spec == OpSpecJump_ReturnToCaller) { - BC_PRINT_OP("ret"); - } - else if (op_spec == OpSpecJump_CallExternal) { - uint32 hashed_name = Read32(); - BC_PRINT_OP("callext %u", hashed_name); - } -} - -void FxScriptBCPrinter::DoData(char* s, uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecData_String) { - uint16 length = Read16(); - char* data_str = FX_SCRIPT_ALLOC_MEMORY(char, length); - uint16* data_str16 = reinterpret_cast(data_str); - - uint32 bytecode_end = mBytecodeIndex + length; - int data_index = 0; - while (mBytecodeIndex < bytecode_end) { - uint16 value16 = Read16(); - data_str16[data_index++] = ((value16 << 8) | (value16 >> 8)); - } - - BC_PRINT_OP("datastr %d, %.*s", length, length, data_str); - - FX_SCRIPT_FREE(char, data_str); - } - else if (op_spec == OpSpecData_ParamsStart) { - BC_PRINT_OP("paramsstart"); - } -} - -void FxScriptBCPrinter::DoType(char* s, uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecType_Int) { - BC_PRINT_OP("typeint"); - } - else if (op_spec == OpSpecType_String) { - BC_PRINT_OP("typestr"); - } -} - -void FxScriptBCPrinter::DoMove(char* s, uint8 op_base, uint8 op_spec_raw) -{ - uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); - uint8 op_reg = (op_spec_raw & 0x0F); - - if (op_spec == OpSpecMove_Int32) { - uint32 value = Read32(); - BC_PRINT_OP("move32 %s, %u\t", FxScriptBCEmitter::GetRegisterName(static_cast(op_reg)), value); - } -} - - -void FxScriptBCPrinter::Print() -{ - while (mBytecodeIndex < mBytecode.Size()) { - PrintOp(); - } -} - -void FxScriptBCPrinter::PrintOp() -{ - uint32 bc_index = mBytecodeIndex; - - uint16 op_full = Read16(); - - const uint8 op_base = static_cast(op_full >> 8); - const uint8 op_spec = static_cast(op_full & 0xFF); - - char s[128]; - - switch (op_base) { - case OpBase_Push: - DoPush(s, op_base, op_spec); - break; - case OpBase_Pop: - DoPop(s, op_base, op_spec); - break; - case OpBase_Load: - DoLoad(s, op_base, op_spec); - break; - case OpBase_Arith: - DoArith(s, op_base, op_spec); - break; - case OpBase_Jump: - DoJump(s, op_base, op_spec); - break; - case OpBase_Save: - DoSave(s, op_base, op_spec); - break; - case OpBase_Data: - DoData(s, op_base, op_spec); - break; - case OpBase_Type: - DoType(s, op_base, op_spec); - break; - case OpBase_Move: - DoMove(s, op_base, op_spec); - break; - } - - printf("%-25s", s); - - printf("\t# Offset: %u\n", bc_index); - -} - - - -/////////////////////////////////////////// -// Bytecode VM -/////////////////////////////////////////// - -void FxScriptVM::PrintRegisters() -{ - printf("\n=== Register Dump ===\n\n"); - printf("X0=%u\tX1=%u\tX2=%u\tX3=%u\n", - Registers[FX_REG_X0], Registers[FX_REG_X1], Registers[FX_REG_X2], Registers[FX_REG_X3]); - - printf("XR=%u\tRA=%u\n", Registers[FX_REG_XR], Registers[FX_REG_RA]); - - printf("\n=====================\n\n"); -} - -uint16 FxScriptVM::Read16() -{ - uint8 lo = mBytecode[mPC++]; - uint8 hi = mBytecode[mPC++]; - - return ((static_cast(lo) << 8) | hi); -} - -uint32 FxScriptVM::Read32() -{ - uint16 lo = Read16(); - uint16 hi = Read16(); - - return ((static_cast(lo) << 16) | hi); -} - -void FxScriptVM::Push16(uint16 value) -{ - uint16* dptr = reinterpret_cast(Stack + Registers[FX_REG_SP]); - (*dptr) = value; - - Registers[FX_REG_SP] += sizeof(uint16); -} - -void FxScriptVM::Push32(uint32 value) -{ - uint32* dptr = reinterpret_cast(Stack + Registers[FX_REG_SP]); - (*dptr) = value; - - Registers[FX_REG_SP] += sizeof(uint32); -} - -uint32 FxScriptVM::Pop32() -{ - if (Registers[FX_REG_SP] == 0) { - printf("ERR\n"); - } - - Registers[FX_REG_SP] -= sizeof(uint32); - - uint32 value = *reinterpret_cast(Stack + Registers[FX_REG_SP]); - return value; -} - - - -FxScriptVMCallFrame& FxScriptVM::PushCallFrame() -{ - mIsInCallFrame = true; - - FxScriptVMCallFrame& start_frame = mCallFrames[mCallFrameIndex++]; - start_frame.StartStackIndex = Registers[FX_REG_SP]; - - return start_frame; -} - -void FxScriptVM::PopCallFrame() -{ - FxScriptVMCallFrame* frame = GetCurrentCallFrame(); - if (!frame) { - - FX_BREAKPOINT; - } - - while (Registers[FX_REG_SP] > frame->StartStackIndex) { - Pop32(); - } - - --mCallFrameIndex; - - if (mCallFrameIndex == 0) { - mIsInCallFrame = false; - } -} - -FxScriptVMCallFrame* FxScriptVM::GetCurrentCallFrame() -{ - if (!mIsInCallFrame || mCallFrameIndex < 1) { - return nullptr; - } - - return &mCallFrames[mCallFrameIndex - 1]; -} - -FxScriptExternalFunc* FxScriptVM::FindExternalAction(FxHash hashed_name) -{ - for (FxScriptExternalFunc& func : mExternalFuncs) { - if (func.HashedName == hashed_name) { - return &func; - } - } - return nullptr; -} - -void FxScriptVM::ExecuteOp() -{ - uint16 op_full = Read16(); - - const uint8 op_base = static_cast(op_full >> 8); - const uint8 op_spec = static_cast(op_full & 0xFF); - - switch (op_base) { - case OpBase_Push: - DoPush(op_base, op_spec); - break; - case OpBase_Pop: - DoPop(op_base, op_spec); - break; - case OpBase_Load: - DoLoad(op_base, op_spec); - break; - case OpBase_Arith: - DoArith(op_base, op_spec); - break; - case OpBase_Jump: - DoJump(op_base, op_spec); - break; - case OpBase_Save: - DoSave(op_base, op_spec); - break; - case OpBase_Data: - DoData(op_base, op_spec); - break; - case OpBase_Type: - DoType(op_base, op_spec); - break; - case OpBase_Move: - DoMove(op_base, op_spec); - break; - } -} - -void FxScriptVM::DoLoad(uint8 op_base, uint8 op_spec_raw) -{ - uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); - uint8 op_reg = (op_spec_raw & 0x0F); - - if (op_spec == OpSpecLoad_Int32) { - int16 offset = Read16(); - - uint8* dataptr = &Stack[Registers[FX_REG_SP] + offset]; - uint32 data32 = *reinterpret_cast(dataptr); - - Registers[op_reg] = data32; - } - else if (op_spec == OpSpecLoad_AbsoluteInt32) { - uint32 offset = Read32(); - - uint8* dataptr = &Stack[offset]; - uint32 data32 = *reinterpret_cast(dataptr); - - Registers[op_reg] = data32; - } -} - -void FxScriptVM::DoPush(uint8 op_base, uint8 op_spec) -{ - if (mIsInParams) { - if (mCurrentType != FxScriptValue::NONETYPE) { - mPushedTypes.Insert(mCurrentType); - mCurrentType = FxScriptValue::NONETYPE; - } - else { - mPushedTypes.Insert(FxScriptValue::INT); - } - } - - if (op_spec == OpSpecPush_Int32) { - uint32 value = Read32(); - Push32(value); - } - else if (op_spec == OpSpecPush_Reg32) { - uint16 reg = Read16(); - Push32(Registers[reg]); - } -} - -void FxScriptVM::DoPop(uint8 op_base, uint8 op_spec_raw) -{ - uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); - uint8 op_reg = (op_spec_raw & 0x0F); - - if (op_spec == OpSpecPush_Int32) { - uint32 value = Pop32(); - - Registers[op_reg] = value; - } - - if (mIsInParams) { - mPushedTypes.RemoveLast(); - } -} - -void FxScriptVM::DoArith(uint8 op_base, uint8 op_spec) -{ - uint8 a_reg = mBytecode[mPC++]; - uint8 b_reg = mBytecode[mPC++]; - - if (op_spec == OpSpecArith_Add) { - Registers[FX_REG_XR] = Registers[a_reg] + Registers[b_reg]; - } -} - -void FxScriptVM::DoSave(uint8 op_base, uint8 op_spec) -{ - uint32 offset; - - if (op_spec == OpSpecSave_AbsoluteInt32 || op_spec == OpSpecSave_AbsoluteReg32) { - offset = Read32(); - } - else { - // The offset is relative to the stack pointer, get the absolute value - const int16 relative_offset = static_cast(Read16()); - offset = static_cast(Registers[FX_REG_SP] + relative_offset); - } - - uint32* dataptr = reinterpret_cast(&Stack[offset]); - - // Save a imm32 into an offset in the stack - if (op_spec == OpSpecSave_Int32) { - (*dataptr) = Read32(); - } - - // Save a register into an offset in the stack - else if (op_spec == OpSpecSave_Reg32) { - uint16 reg = Read16(); - (*dataptr) = Registers[reg]; - } - - else if (op_spec == OpSpecSave_AbsoluteInt32) { - (*dataptr) = Read32(); - } - - else if (op_spec == OpSpecSave_AbsoluteReg32) { - uint16 reg = Read16(); - (*dataptr) = Registers[reg]; - } -} - -void FxScriptVM::DoJump(uint8 op_base, uint8 op_spec) -{ - - if (op_spec == OpSpecJump_Relative) { - uint16 offset = Read16(); - mPC += offset; - } - else if (op_spec == OpSpecJump_Absolute) { - uint32 position = Read32(); - mPC = position; - } - else if (op_spec == OpSpecJump_AbsoluteReg32) { - uint16 reg = Read16(); - mPC = Registers[reg]; - } - else if (op_spec == OpSpecJump_CallAbsolute) { - uint32 call_address = Read32(); - //printf("Call to address % 4u\n", call_address); - - //Push32(mPC); - - Registers[FX_REG_RA] = mPC; - - mPushedTypes.Clear(); - mIsInParams = false; - - PushCallFrame(); - - // Jump to the action address - mPC = call_address; - } - else if (op_spec == OpSpecJump_ReturnToCaller) { - PopCallFrame(); - - //uint32 return_address = Pop32(); - - uint32 return_address = Registers[FX_REG_RA]; - //printf("Return to caller (%04d)\n", return_address); - mPC = return_address; - - // Restore the return address register to its previous value. This is pushed when `paramsstart` is encountered. - //Registers[FX_REG_RA] = Pop32(); - } - else if (op_spec == OpSpecJump_CallExternal) { - uint32 hashed_name = Read32(); - - FxScriptExternalFunc* external_func = FindExternalAction(hashed_name); - - if (!external_func) { - printf("!!! Could not find external function in VM!\n"); - return; - } - - std::vector params; - params.reserve(external_func->ParameterTypes.size()); - - uint32 num_params = mPushedTypes.Size(); - - printf("Num Params: %u\n", num_params); - - for (int i = 0; i < num_params; i++) { - FxScriptValue::ValueType param_type = mPushedTypes.GetLast(); - - FxScriptValue value; - value.Type = param_type; - - if (param_type == FxScriptValue::INT) { - value.ValueInt = Pop32(); - value.Type = param_type; - } - else if (param_type == FxScriptValue::STRING) { - uint32 string_location = Pop32(); - uint8* str_base_ptr = &mBytecode[string_location]; - // uint16 str_length = *((uint16*)str_base_ptr); - - str_base_ptr += 2; - - value.ValueString = reinterpret_cast(str_base_ptr); - value.Type = param_type; - } - - mPushedTypes.RemoveLast(); - - params.push_back(value); - } - - mPushedTypes.Clear(); - mIsInParams = false; - - FxScriptValue return_value{}; - external_func->Function(this, params, &return_value); - } -} - -void FxScriptVM::DoData(uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecData_String) { - uint16 length = Read16(); - mPC += length; - } - else if (op_spec == OpSpecData_ParamsStart) { - mIsInParams = true; - - // Push the current return address pointer. This is so nested action calls can correctly navigate back - // to the caller. - //Push32(Registers[FX_REG_RA]); - } -} - -void FxScriptVM::DoType(uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecType_Int) { - mCurrentType = FxScriptValue::INT; - } - else if (op_spec == OpSpecType_String) { - mCurrentType = FxScriptValue::STRING; - } -} - -void FxScriptVM::DoMove(uint8 op_base, uint8 op_spec_raw) -{ - uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); - FxScriptRegister op_reg = static_cast(op_spec_raw & 0x0F); - - if (op_spec == OpSpecMove_Int32) { - int32 value = Read32(); - Registers[op_reg] = value; - } -} - - - -////////////////////////////////////////////////// -// Script Bytecode to x86 Transpiler -////////////////////////////////////////////////// - -uint16 FxScriptTranspilerX86::Read16() -{ - uint8 lo = mBytecode[mBytecodeIndex++]; - uint8 hi = mBytecode[mBytecodeIndex++]; - - return ((static_cast(lo) << 8) | hi); -} - -uint32 FxScriptTranspilerX86::Read32() -{ - uint16 lo = Read16(); - uint16 hi = Read16(); - - return ((static_cast(lo) << 16) | hi); -} - -static const char* GetX86Register(FxScriptRegister reg) -{ - switch (reg) { - case FX_REG_X0: - return "eax"; - case FX_REG_X1: - return "ebx"; - case FX_REG_X2: - return "ecx"; - case FX_REG_X3: - return "edx"; - case FX_REG_SP: - return "esp"; - case FX_REG_RA: - return "esi"; - case FX_REG_XR: - return "eax"; - default:; - } - return "UNKNOWN"; -} - -//#define BC_PRINT_OP(fmt_, ...) snprintf(s, 128, fmt_, ##__VA_ARGS__) - -void FxScriptTranspilerX86::DoLoad(char* s, uint8 op_base, uint8 op_spec_raw) -{ - uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); - uint8 op_reg = (op_spec_raw & 0x0F); - - if (op_spec == OpSpecLoad_Int32) { - int16 offset = Read16(); - // load32 [off32] [%r32] - offset += 8; - StrOut("mov %s, [ebp %c %d]", GetX86Register(static_cast(op_reg)), (offset <= 0 ? '+' : '-'), abs(offset)); - } - else if (op_spec == OpSpecLoad_AbsoluteInt32) { - uint32 offset = Read32(); - //StrOut("load32a %u, %s", offset, GetX86Register(static_cast(op_reg))); - StrOut("mov %s, [esi + %u]", GetX86Register(static_cast(op_reg)), offset); - - } -} - -void FxScriptTranspilerX86::DoPush(char* s, uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecPush_Int32) { - uint32 value = Read32(); - // push32 [imm32] - StrOut("push dword %u", value); - } - else if (op_spec == OpSpecPush_Reg32) { - uint16 reg = Read16(); - // push32r [%reg32] - StrOut("push %s", GetX86Register(static_cast(reg))); - } - - if (mIsInAction) { - mSizePushedInAction += 4; - } -} - -void FxScriptTranspilerX86::DoPop(char* s, uint8 op_base, uint8 op_spec_raw) -{ - uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); - uint8 op_reg = (op_spec_raw & 0x0F); - - if (op_spec == OpSpecPush_Int32) { - // pop32 [%reg32] - StrOut("pop %s", GetX86Register(static_cast(op_reg))); - } -} - -void FxScriptTranspilerX86::DoArith(char* s, uint8 op_base, uint8 op_spec) -{ - uint8 a_reg = mBytecode[mBytecodeIndex++]; - uint8 b_reg = mBytecode[mBytecodeIndex++]; - - if (op_spec == OpSpecArith_Add) { - // add32 [%reg32] [%reg32] - StrOut("add %s, %s", GetX86Register(static_cast(a_reg)), GetX86Register(static_cast(b_reg))); - } -} - -void FxScriptTranspilerX86::DoSave(char* s, uint8 op_base, uint8 op_spec) -{ - - // Save a imm32 into an offset in the stack - if (op_spec == OpSpecSave_Int32) { - const int16 offset = Read16() + 8; - const int32 value = Read32(); - - StrOut("mov dword [ebp %c %d], %d", (offset <= 0 ? '+' : '-'), abs(offset), value); - //BC_PRINT_OP("save32 %d, %u", offset, value); - } - - // Save a register into an offset in the stack - else if (op_spec == OpSpecSave_Reg32) { - int16 offset = Read16(); - uint16 reg = Read16(); - - offset += 8; - - StrOut("mov [ebp %c %d], %s", (offset <= 0 ? '+' : '-'), abs(offset), GetX86Register(static_cast(reg))); - //BC_PRINT_OP("save32r %d, %s", offset, GetX86Register(static_cast(reg))); - } - else if (op_spec == OpSpecSave_AbsoluteInt32) { - const uint32 offset = Read32(); - const int32 value = Read32(); - - //StrOut("save32a %u, %u", offset, value); - StrOut("mov [esi %c %d], %d", (offset <= 0 ? '+' : '-'), offset, value); - } - else if (op_spec == OpSpecSave_AbsoluteReg32) { - const uint32 offset = Read32(); - uint16 reg = Read16(); - - //StrOut("save32ar %u, %s", offset, GetX86Register(static_cast(reg))); - StrOut("mov [esi %c %d], %s", (offset <= 0 ? '+' : '-'), offset, GetX86Register(static_cast(reg))); - } -} - -void FxScriptTranspilerX86::DoJump(char* s, uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecJump_Relative) { - uint16 offset = Read16(); - - mIsInAction = true; - - StrOut("jmp _A_%u", mBytecodeIndex + offset); - - if (mTextIndent) { - --mTextIndent; - } - StrOut("_L_%u:", mBytecodeIndex); - - ++mTextIndent; - - - StrOut("pop esi ; save return address"); - StrOut("mov ebp, esp"); - - StrOut(""); - } - else if (op_spec == OpSpecJump_Absolute) { - uint32 position = Read32(); - StrOut("jmpa %u", position); - } - else if (op_spec == OpSpecJump_AbsoluteReg32) { - uint16 reg = Read16(); - StrOut("jmpar %s", GetX86Register(static_cast(reg))); - } - else if (op_spec == OpSpecJump_CallAbsolute) { - uint32 position = Read32(); - //BC_PRINT_OP("calla %u", position); - StrOut("call _L_%u", position); - } - else if (op_spec == OpSpecJump_ReturnToCaller) { - StrOut("add esp, %u", mSizePushedInAction); - mSizePushedInAction = 0; - mIsInAction = false; - - StrOut(""); - StrOut("push esi ; push return address"); - StrOut("ret"); - - --mTextIndent; - - StrOut("_A_%u:", mBytecodeIndex); - - ++mTextIndent; - } - else if (op_spec == OpSpecJump_CallExternal) { - uint32 hashed_name = Read32(); - //StrOut("callext %u", hashed_name); - - StrOut("nop ; callext %u", hashed_name); - } -} - -void FxScriptTranspilerX86::DoData(char* s, uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecData_String) { - uint16 length = Read16(); - char* data_str = FX_SCRIPT_ALLOC_MEMORY(char, length); - uint16* data_str16 = reinterpret_cast(data_str); - - uint32 bytecode_end = mBytecodeIndex + length; - int data_index = 0; - while (mBytecodeIndex < bytecode_end) { - uint16 value16 = Read16(); - data_str16[data_index++] = ((value16 << 8) | (value16 >> 8)); - } - - StrOut("datastr %d, %.*s", length, length, data_str); - - FX_SCRIPT_FREE(char, data_str); - } - else if (op_spec == OpSpecData_ParamsStart) { - StrOut("; Parameters start"); - //BC_PRINT_OP("paramsstart"); - } -} - -void FxScriptTranspilerX86::DoType(char* s, uint8 op_base, uint8 op_spec) -{ - if (op_spec == OpSpecType_Int) { - //BC_PRINT_OP("typeint"); - } - else if (op_spec == OpSpecType_String) { - //BC_PRINT_OP("typestr"); - } -} - -void FxScriptTranspilerX86::DoMove(char* s, uint8 op_base, uint8 op_spec_raw) -{ - uint8 op_spec = ((op_spec_raw >> 4) & 0x0F); - FxScriptRegister op_reg = static_cast(op_spec_raw & 0x0F); - - if (op_spec == OpSpecMove_Int32) { - int32 value = Read32(); - //StrOut("move32 %s, %u", FxScriptBCEmitter::GetRegisterName(op_reg), value); - StrOut("mov %s, %d", GetX86Register(op_reg), value); - } -} - - -void FxScriptTranspilerX86::Print() -{ - // Print header - StrOut("section .text"); - StrOut("global _start"); - StrOut("_start:"); - - - while (mBytecodeIndex < mBytecode.Size()) { - PrintOp(); - } - - // Print footer - - StrOut("mov ebx, eax"); - StrOut("mov eax, 1"); - StrOut("int 0x80"); -} - -#include - -void FxScriptTranspilerX86::StrOut(const char* fmt, ...) -{ - for (int i = 0; i < mTextIndent; i++) { - putchar('\t'); - } - - va_list args; - va_start(args, fmt); - vprintf(fmt, args); - va_end(args); - - puts(""); -} - -void FxScriptTranspilerX86::PrintOp() -{ - // uint32 bc_index = mBytecodeIndex; - - uint16 op_full = Read16(); - - const uint8 op_base = static_cast(op_full >> 8); - const uint8 op_spec = static_cast(op_full & 0xFF); - - char s[128]; - - switch (op_base) { - case OpBase_Push: - DoPush(s, op_base, op_spec); - break; - case OpBase_Pop: - DoPop(s, op_base, op_spec); - break; - case OpBase_Load: - DoLoad(s, op_base, op_spec); - break; - case OpBase_Arith: - DoArith(s, op_base, op_spec); - break; - case OpBase_Jump: - DoJump(s, op_base, op_spec); - break; - case OpBase_Save: - DoSave(s, op_base, op_spec); - break; - case OpBase_Data: - DoData(s, op_base, op_spec); - break; - case OpBase_Type: - DoType(s, op_base, op_spec); - break; - case OpBase_Move: - DoMove(s, op_base, op_spec); - break; - } - //printf("%-25s\n", s); - -} diff --git a/FxScript.hpp b/FxScript.hpp deleted file mode 100644 index 8e1c216..0000000 --- a/FxScript.hpp +++ /dev/null @@ -1,1029 +0,0 @@ -#pragma once - -#include - -#include "FxMPPagedArray.hpp" -#include "FxTokenizer.hpp" - -#define FX_SCRIPT_VERSION_MAJOR 0 -#define FX_SCRIPT_VERSION_MINOR 3 -#define FX_SCRIPT_VERSION_PATCH 1 - -#define FX_SCRIPT_VAR_RETURN_VAL "__ReturnVal__" - - -struct FxAstVarRef; -struct FxScriptScope; -struct FxScriptAction; - -struct FxScriptValue -{ - static FxScriptValue None; - - enum ValueType : uint16 - { - NONETYPE = 0x00, - INT = 0x01, - FLOAT = 0x02, - STRING = 0x04, - VEC3 = 0x08, - REF = 0x10 - }; - - ValueType Type = NONETYPE; - - union - { - int ValueInt = 0; - float ValueFloat; - float ValueVec3[3]; - char* ValueString; - - FxAstVarRef* ValueRef; - }; - - FxScriptValue() - { - } - - explicit FxScriptValue(ValueType type, int value) - : Type(type), ValueInt(value) - { - } - - explicit FxScriptValue(ValueType type, float value) - : Type(type), ValueFloat(value) - { - } - - FxScriptValue(const FxScriptValue& other) - { - Type = other.Type; - if (other.Type == INT) { - ValueInt = other.ValueInt; - } - else if (other.Type == FLOAT) { - ValueFloat = other.ValueFloat; - } - else if (other.Type == STRING) { - ValueString = other.ValueString; - } - else if (other.Type == REF) { - ValueRef = other.ValueRef; - } - } - - void Print() const - { - printf("[Value: "); - if (Type == NONETYPE) { - printf("Null]\n"); - } - else if (Type == INT) { - printf("Int, %d]\n", ValueInt); - } - else if (Type == FLOAT) { - printf("Float, %f]\n", ValueFloat); - } - else if (Type == STRING) { - printf("String, %s]\n", ValueString); - } - else if (Type == REF) { - printf("Ref, %p]\n", ValueRef); - } - } - - inline bool IsNumber() - { - return (Type == INT || Type == FLOAT); - } - - inline bool IsRef() - { - return (Type == REF); - } -}; - -enum FxAstType -{ - FX_AST_LITERAL, - //FX_AST_NAME, - - FX_AST_BINOP, - FX_AST_UNARYOP, - FX_AST_BLOCK, - - // Variables - FX_AST_VARREF, - FX_AST_VARDECL, - FX_AST_ASSIGN, - - // Actions - FX_AST_ACTIONDECL, - FX_AST_ACTIONCALL, - FX_AST_RETURN, - - FX_AST_DOCCOMMENT, - - FX_AST_COMMANDMODE, -}; - -struct FxAstNode -{ - FxAstType NodeType; -}; - - -struct FxAstLiteral : public FxAstNode -{ - FxAstLiteral() - { - this->NodeType = FX_AST_LITERAL; - } - - //FxTokenizer::Token* Token = nullptr; - FxScriptValue Value; -}; - -struct FxAstBinop : public FxAstNode -{ - FxAstBinop() - { - this->NodeType = FX_AST_BINOP; - } - - FxTokenizer::Token* OpToken = nullptr; - FxAstNode* Left = nullptr; - FxAstNode* Right = nullptr; -}; - -struct FxAstBlock : public FxAstNode -{ - FxAstBlock() - { - this->NodeType = FX_AST_BLOCK; - } - - std::vector Statements; -}; - -struct FxAstVarRef : public FxAstNode -{ - FxAstVarRef() - { - this->NodeType = FX_AST_VARREF; - } - - FxTokenizer::Token* Name = nullptr; - FxScriptScope* Scope = nullptr; -}; - -struct FxAstAssign : public FxAstNode -{ - FxAstAssign() - { - this->NodeType = FX_AST_ASSIGN; - } - - FxAstVarRef* Var = nullptr; - //FxScriptValue Value; - FxAstNode* Rhs = nullptr; -}; - -struct FxAstVarDecl : public FxAstNode -{ - FxAstVarDecl() - { - this->NodeType = FX_AST_VARDECL; - } - - FxTokenizer::Token* Name = nullptr; - FxTokenizer::Token* Type = nullptr; - FxAstAssign* Assignment = nullptr; - - /// Ignore the scope that the variable is declared in, force it to be global. - bool DefineAsGlobal = false; -}; - -struct FxAstDocComment : public FxAstNode -{ - FxAstDocComment() - { - this->NodeType = FX_AST_DOCCOMMENT; - } - - FxTokenizer::Token* Comment; -}; - -struct FxAstActionDecl : public FxAstNode -{ - FxAstActionDecl() - { - this->NodeType = FX_AST_ACTIONDECL; - } - - FxTokenizer::Token* Name = nullptr; - FxAstVarDecl* ReturnVar = nullptr; - FxAstBlock* Params = nullptr; - FxAstBlock* Block = nullptr; - - std::vector DocComments; -}; - -struct FxAstCommandMode : public FxAstNode -{ - FxAstCommandMode() - { - this->NodeType = FX_AST_COMMANDMODE; - } - - FxAstNode* Node = nullptr; -}; - -struct FxAstActionCall : public FxAstNode -{ - FxAstActionCall() - { - this->NodeType = FX_AST_ACTIONCALL; - } - - FxScriptAction* Action = nullptr; - FxHash HashedName = 0; - std::vector Params{}; // FxAstLiteral or FxAstVarRef -}; - -struct FxAstReturn : public FxAstNode -{ - FxAstReturn() - { - this->NodeType = FX_AST_RETURN; - } -}; - -/** - * @brief Data is accessible from a label, such as a variable or an action. - */ -struct FxScriptLabelledData -{ - FxHash HashedName = 0; - FxTokenizer::Token* Name = nullptr; - - FxScriptScope* Scope = nullptr; -}; - -struct FxScriptAction : public FxScriptLabelledData -{ - FxScriptAction(FxTokenizer::Token* name, FxScriptScope* scope, FxAstBlock* block, FxAstActionDecl* declaration) - { - HashedName = name->GetHash(); - Name = name; - Scope = scope; - Block = block; - Declaration = declaration; - } - - FxAstActionDecl* Declaration = nullptr; - FxAstBlock* Block = nullptr; -}; - -struct FxScriptVar : public FxScriptLabelledData -{ - FxTokenizer::Token* Type = nullptr; - FxScriptValue Value; - - bool IsExternal = false; - - void Print() const - { - printf("[Var] Type: %.*s, Name: %.*s (Hash:%u)", Type->Length, Type->Start, Name->Length, Name->Start, Name->GetHash()); - Value.Print(); - } - - FxScriptVar() - { - } - - FxScriptVar(FxTokenizer::Token* type, FxTokenizer::Token* name, FxScriptScope* scope, bool is_external = false) - : Type(type) - { - this->HashedName = name->GetHash(); - this->Name = name; - this->Scope = scope; - IsExternal = is_external; - } - - FxScriptVar(const FxScriptVar& other) - { - HashedName = other.HashedName; - Type = other.Type; - Name = other.Name; - Value = other.Value; - IsExternal = other.IsExternal; - } - - FxScriptVar& operator = (FxScriptVar&& other) noexcept - { - HashedName = other.HashedName; - Type = other.Type; - Name = other.Name; - Value = other.Value; - IsExternal = other.IsExternal; - - Name = nullptr; - Type = nullptr; - HashedName = 0; - - return *this; - } - - ~FxScriptVar() - { - if (!IsExternal) { - return; - } - - // Free tokens allocated by external variables - if (Type && Type->Start) { - FX_SCRIPT_FREE(char, Type->Start); - } - - if (this->Name && this->Name->Start) { - FX_SCRIPT_FREE(char, this->Name->Start); - } - } -}; - -class FxScriptInterpreter; -class FxScriptVM; - - -struct FxScriptExternalFunc -{ - //using FuncType = void (*)(FxScriptInterpreter& interpreter, std::vector& params, FxScriptValue* return_value); - - using FuncType = void (*)(FxScriptVM* vm, std::vector& params, FxScriptValue* return_value); - - FxHash HashedName = 0; - FuncType Function = nullptr; - - std::vector ParameterTypes; - bool IsVariadic = false; -}; - -struct FxScriptScope -{ - FxMPPagedArray Vars; - FxMPPagedArray Actions; - - FxScriptScope* Parent = nullptr; - - // This points to the return value for the current scope. If an action returns a value, - // this will be set to the variable that holds its value. This is interpreter only. - FxScriptVar* ReturnVar = nullptr; - - void PrintAllVarsInScope() - { - puts("\n=== SCOPE ==="); - for (FxScriptVar& var : Vars) { - var.Print(); - } - } - - FxScriptVar* FindVarInScope(FxHash hashed_name) - { - return FindInScope(hashed_name, Vars); - } - - FxScriptAction* FindActionInScope(FxHash hashed_name) - { - return FindInScope(hashed_name, Actions); - } - - template requires std::is_base_of_v - T* FindInScope(FxHash hashed_name, const FxMPPagedArray& buffer) - { - for (T& var : buffer) { - if (var.HashedName == hashed_name) { - return &var; - } - } - - return nullptr; - } -}; - -class FxScriptInterpreter; - -class FxConfigScript -{ - using Token = FxTokenizer::Token; - using TT = FxTokenizer::TokenType; - -public: - FxConfigScript() = default; - - void LoadFile(const char* path); - - void PushScope(); - void PopScope(); - - FxScriptVar* FindVar(FxHash hashed_name); - - FxScriptAction* FindAction(FxHash hashed_name); - FxScriptExternalFunc* FindExternalAction(FxHash hashed_name); - - FxAstNode* TryParseKeyword(FxAstBlock* parent_block); - - FxAstAssign* TryParseAssignment(FxTokenizer::Token* var_name); - - FxScriptValue ParseValue(); - - FxAstActionDecl* ParseActionDeclare(); - - FxAstNode* ParseRhs(); - FxAstActionCall* ParseActionCall(); - - /** - * @brief Declares a variable for internal uses as if it was declared in the script. - * @param name Name of the variable - * @param type The name of the type - * @param scope The scope the variable will be declared in - * @return - */ - FxAstVarDecl* InternalVarDeclare(FxTokenizer::Token* name_token, FxTokenizer::Token* type_token, FxScriptScope* scope = nullptr); - FxAstVarDecl* ParseVarDeclare(FxScriptScope* scope = nullptr); - - FxAstBlock* ParseBlock(); - - FxAstNode* ParseStatement(FxAstBlock* parent_block); - FxAstNode* ParseStatementAsCommand(FxAstBlock* parent_block); - - FxAstBlock* Parse(); - - /** - * @brief Parses and executes a script. - * @param interpreter The interpreter to execute with - */ - void Execute(FxScriptVM& vm); - - /** - * @brief Executes a command on a script. Defaults to parsing with command style syntax. - * @param command The command to execute on the script. - * @return If the command has been executed - */ - bool ExecuteUserCommand(const char* command, FxScriptInterpreter& interpreter); - - Token& GetToken(int offset = 0); - Token& EatToken(TT token_type); - - void RegisterExternalFunc(FxHash func_name, std::vector param_types, FxScriptExternalFunc::FuncType func, bool is_variadic); - - void DefineExternalVar(const char* type, const char* name, const FxScriptValue& value); - -private: - template requires std::is_base_of_v - T* FindLabelledData(FxHash hashed_name, FxMPPagedArray& buffer) - { - FxScriptScope* scope = mCurrentScope; - - while (scope) { - T* var = scope->FindInScope(hashed_name, buffer); - if (var) { - return var; - } - - scope = scope->Parent; - } - - return nullptr; - } - - void DefineDefaultExternalFunctions(); - - Token* CreateTokenFromString(FxTokenizer::TokenType type, const char* text); - void CreateInternalVariableTokens(); - -private: - FxMPPagedArray mScopes; - FxScriptScope* mCurrentScope; - - std::vector mExternalFuncs; - - std::vector CurrentDocComments; - - FxAstBlock* mRootBlock = nullptr; - - bool mHasErrors = false; - bool mInCommandMode = false; - - char* mFileData; - FxMPPagedArray mTokens = {}; - uint32 mTokenIndex = 0; - - // Name tokens for internal variables - Token* mTokenReturnVar = nullptr; - -}; - -////////////////////////////////// -// Script AST Printer -////////////////////////////////// - -class FxAstPrinter -{ -public: - FxAstPrinter(FxAstBlock* root_block) - //: mRootBlock(root_block) - { - } - - void Print(FxAstNode* node, int depth = 0) - { - if (node == nullptr) { - return; - } - - for (int i = 0; i < depth; i++) { - putchar(' '); - putchar(' '); - } - - if (node->NodeType == FX_AST_BLOCK) { - puts("[BLOCK]"); - - FxAstBlock* block = reinterpret_cast(node); - for (FxAstNode* child : block->Statements) { - Print(child, depth + 1); - } - return; - } - else if (node->NodeType == FX_AST_ACTIONDECL) { - FxAstActionDecl* actiondecl = reinterpret_cast(node); - printf("[ACTIONDECL] "); - actiondecl->Name->Print(); - - for (FxAstNode* param : actiondecl->Params->Statements) { - Print(param, depth + 1); - } - - Print(actiondecl->Block, depth + 1); - } - else if (node->NodeType == FX_AST_VARDECL) { - FxAstVarDecl* vardecl = reinterpret_cast(node); - - printf("[VARDECL] "); - vardecl->Name->Print(); - - Print(vardecl->Assignment, depth + 1); - } - else if (node->NodeType == FX_AST_ASSIGN) { - FxAstAssign* assign = reinterpret_cast(node); - - printf("[ASSIGN] "); - - assign->Var->Name->Print(); - Print(assign->Rhs, depth + 1); - } - else if (node->NodeType == FX_AST_ACTIONCALL) { - FxAstActionCall* actioncall = reinterpret_cast(node); - - printf("[ACTIONCALL] "); - if (actioncall->Action == nullptr) { - printf("{defined externally}"); - } - else { - actioncall->Action->Name->Print(true); - } - - printf(" (%zu params)\n", actioncall->Params.size()); - } - else if (node->NodeType == FX_AST_LITERAL) { - FxAstLiteral* literal = reinterpret_cast(node); - - printf("[LITERAL] "); - literal->Value.Print(); - } - else if (node->NodeType == FX_AST_BINOP) { - FxAstBinop* binop = reinterpret_cast(node); - - printf("[BINOP] "); - binop->OpToken->Print(); - - Print(binop->Left, depth + 1); - Print(binop->Right, depth + 1); - } - else if (node->NodeType == FX_AST_COMMANDMODE) { - FxAstCommandMode* command_mode = reinterpret_cast(node); - printf("[COMMANDMODE]\n"); - - Print(command_mode->Node, depth + 1); - } - else if (node->NodeType == FX_AST_RETURN) { - puts("[RETURN]"); - } - else { - puts("[UNKNOWN]"); - } - //else if (node->NodeType == FX_AST_) - } - -public: - //FxAstBlock* mRootBlock = nullptr; -}; - -///////////////////////////////////////////// -// Script Bytecode Emitter -///////////////////////////////////////////// - -enum FxScriptRegister : uint8 -{ - FX_REG_NONE = 0x00, - FX_REG_X0, - FX_REG_X1, - FX_REG_X2, - FX_REG_X3, - - /** - * @brief Return address register. - */ - FX_REG_RA, - - /** - * @brief Register that contains the result of an operation. - */ - FX_REG_XR, - - /** - * @brief Register that contains the stack pointer for the VM. - */ - FX_REG_SP, - - FX_REG_SIZE, -}; - -enum FxScriptRegisterFlag : uint16 -{ - FX_REGFLAG_NONE = 0x00, - FX_REGFLAG_X0 = 0x01, - FX_REGFLAG_X1 = 0x02, - FX_REGFLAG_X2 = 0x04, - FX_REGFLAG_X3 = 0x08, - FX_REGFLAG_RA = 0x10, - FX_REGFLAG_XR = 0x20, -}; - -inline FxScriptRegisterFlag operator | (FxScriptRegisterFlag a, FxScriptRegisterFlag b) -{ - return static_cast(static_cast(a) | static_cast(b)); -} - -inline FxScriptRegisterFlag operator & (FxScriptRegisterFlag a, FxScriptRegisterFlag b) -{ - return static_cast(static_cast(a) & static_cast(b)); -} - -struct FxScriptBytecodeVarHandle -{ - FxHash HashedName = 0; - FxScriptValue::ValueType Type = FxScriptValue::INT; - int64 Offset = 0; - - uint16 SizeOnStack = 4; - uint32 ScopeIndex = 0; -}; - -struct FxScriptBytecodeActionHandle -{ - FxHash HashedName = 0; - uint32 BytecodeIndex = 0; -}; - -class FxScriptBCEmitter -{ -public: - FxScriptBCEmitter() = default; - - void BeginEmitting(FxAstNode* node); - void Emit(FxAstNode* node); - - enum RhsMode - { - RHS_FETCH_TO_REGISTER, - - /** - * @brief Pushes the value to the stack, assuming that the value does not exist yet. - */ - RHS_DEFINE_IN_MEMORY, - - RHS_ASSIGN_TO_HANDLE, - }; - - static FxScriptRegister RegFlagToReg(FxScriptRegisterFlag reg_flag); - static FxScriptRegisterFlag RegToRegFlag(FxScriptRegister reg); - - static const char* GetRegisterName(FxScriptRegister reg); - - FxMPPagedArray mBytecode{}; - - enum VarDeclareMode { - DECLARE_DEFAULT, - DECLARE_NO_EMIT, - }; - - -private: - void EmitBlock(FxAstBlock* block); - void EmitAction(FxAstActionDecl* action); - void DoActionCall(FxAstActionCall* call); - FxScriptBytecodeVarHandle* DoVarDeclare(FxAstVarDecl* decl, VarDeclareMode mode = DECLARE_DEFAULT); - void EmitAssign(FxAstAssign* assign); - FxScriptBytecodeVarHandle* DefineAndFetchParam(FxAstNode* param_decl_node); - FxScriptBytecodeVarHandle* DefineReturnVar(FxAstVarDecl* decl); - - FxScriptRegister EmitVarFetch(FxAstVarRef* ref, RhsMode mode); - - void DoLoad(uint32 stack_offset, FxScriptRegister output_reg, bool force_absolute = false); - void DoSaveInt32(uint32 stack_offset, uint32 value, bool force_absolute = false); - void DoSaveReg32(uint32 stack_offset, FxScriptRegister reg, bool force_absolute = false); - - void EmitPush32(uint32 value); - void EmitPush32r(FxScriptRegister reg); - - void EmitPop32(FxScriptRegister output_reg); - - void EmitLoad32(int offset, FxScriptRegister output_reg); - void EmitLoadAbsolute32(uint32 position, FxScriptRegister output_reg); - - void EmitSave32(int16 offset, uint32 value); - void EmitSaveReg32(int16 offset, FxScriptRegister reg); - - void EmitSaveAbsolute32(uint32 offset, uint32 value); - void EmitSaveAbsoluteReg32(uint32 offset, FxScriptRegister reg); - - void EmitJumpRelative(uint16 offset); - void EmitJumpAbsolute(uint32 position); - void EmitJumpAbsoluteReg32(FxScriptRegister reg); - void EmitJumpCallAbsolute(uint32 position); - void EmitJumpReturnToCaller(); - void EmitJumpCallExternal(FxHash hashed_name); - - void EmitMoveInt32(FxScriptRegister reg, uint32 value); - - void EmitParamsStart(); - void EmitType(FxScriptValue::ValueType type); - - uint32 EmitDataString(char* str, uint16 length); - - FxScriptRegister EmitBinop(FxAstBinop* binop, FxScriptBytecodeVarHandle* handle); - - FxScriptRegister EmitRhs(FxAstNode* rhs, RhsMode mode, FxScriptBytecodeVarHandle* handle); - - FxScriptRegister EmitLiteralInt(FxAstLiteral* literal, RhsMode mode, FxScriptBytecodeVarHandle* handle); - FxScriptRegister EmitLiteralString(FxAstLiteral* literal, RhsMode mode, FxScriptBytecodeVarHandle* handle); - - - void WriteOp(uint8 base_op, uint8 spec_op); - void Write16(uint16 value); - void Write32(uint32 value); - - FxScriptRegister FindFreeRegister(); - - FxScriptBytecodeVarHandle* FindVarHandle(FxHash hashed_name); - FxScriptBytecodeActionHandle* FindActionHandle(FxHash hashed_name); - - void PrintBytecode(); - - void MarkRegisterUsed(FxScriptRegister reg); - void MarkRegisterFree(FxScriptRegister reg); - -public: - - FxMPPagedArray VarHandles; - std::vector ActionHandles; -private: - - FxScriptRegisterFlag mRegsInUse = FX_REGFLAG_NONE; - - int64 mStackOffset = 0; - uint32 mStackSize = 0; - - uint16 mScopeIndex = 0; -}; - -class FxScriptBCPrinter -{ -public: - FxScriptBCPrinter(FxMPPagedArray& bytecode) - { - mBytecode = bytecode; - mBytecode.DoNotDestroy = true; - } - - void Print(); - void PrintOp(); - - -private: - uint16 Read16(); - uint32 Read32(); - - void DoPush(char* s, uint8 op_base, uint8 op_spec); - void DoPop(char* s, uint8 op_base, uint8 op_spec); - void DoLoad(char* s, uint8 op_base, uint8 op_spec); - void DoArith(char* s, uint8 op_base, uint8 op_spec); - void DoSave(char* s, uint8 op_base, uint8 op_spec); - void DoJump(char* s, uint8 op_base, uint8 op_spec); - void DoData(char* s, uint8 op_base, uint8 op_spec); - void DoType(char* s, uint8 op_base, uint8 op_spec); - void DoMove(char* s, uint8 op_base, uint8 op_spec); - -private: - uint32 mBytecodeIndex = 0; - FxMPPagedArray mBytecode; -}; - - -/////////////////////////////////////////// -// Bytecode VM -/////////////////////////////////////////// - -struct FxScriptVMCallFrame -{ - uint32 StartStackIndex = 0; -}; - -class FxScriptVM -{ -public: - FxScriptVM() = default; - - void Start(FxMPPagedArray&& bytecode) - { - mBytecode = std::move(bytecode); - mPushedTypes.Create(64); - - Stack = FX_SCRIPT_ALLOC_MEMORY(uint8, 1024); - //mStackOffset = 0; - Registers[FX_REG_SP] = 0; - memset(Registers, 0, sizeof(Registers)); - - while (mPC < mBytecode.Size()) { - ExecuteOp(); - } - - PrintRegisters(); - } - - void PrintRegisters(); - - void Push16(uint16 value); - void Push32(uint32 value); - - uint32 Pop32(); - -private: - void ExecuteOp(); - - void DoPush(uint8 op_base, uint8 op_spec); - void DoPop(uint8 op_base, uint8 op_spec); - void DoLoad(uint8 op_base, uint8 op_spec); - void DoArith(uint8 op_base, uint8 op_spec); - void DoSave(uint8 op_base, uint8 op_spec); - void DoJump(uint8 op_base, uint8 op_spec); - void DoData(uint8 op_base, uint8 op_spec); - void DoType(uint8 op_base, uint8 op_spec); - void DoMove(uint8 op_base, uint8 op_spec); - - uint16 Read16(); - uint32 Read32(); - - FxScriptVMCallFrame& PushCallFrame(); - FxScriptVMCallFrame* GetCurrentCallFrame(); - void PopCallFrame(); - - FxScriptExternalFunc* FindExternalAction(FxHash hashed_name); - -public: - // NONE, X0, X1, X2, X3, RA, XR, SP - int32 Registers[FX_REG_SIZE]; - - uint8* Stack = nullptr; - - std::vector mExternalFuncs; - - FxMPPagedArray mBytecode; - -private: - uint32 mPC = 0; - - - bool mIsInCallFrame = false; - - FxScriptVMCallFrame mCallFrames[8]; - int mCallFrameIndex = 0; - - bool mIsInParams = false; - FxMPPagedArray mPushedTypes; - - FxScriptValue::ValueType mCurrentType = FxScriptValue::NONETYPE; -}; - -//////////////////////////////////////////////// -// Script Interpreter -//////////////////////////////////////////////// - -class FxScriptInterpreter -{ -public: - FxScriptInterpreter() = default; - - void PushScope(); - void PopScope(); - - FxScriptVar* FindVar(FxHash hashed_name); - FxScriptAction* FindAction(FxHash hashed_name); - FxScriptExternalFunc* FindExternalAction(FxHash hashed_name); - - /** - * @brief Evaluates and gets the immediate value if `value` is a reference, or returns the value if it is already immediate. - * @param value The value to query from - * @return the immediate(literal) value - */ - const FxScriptValue& GetImmediateValue(const FxScriptValue& value); - - void DefineExternalVar(const char* type, const char* name, const FxScriptValue& value); - -private: - friend class FxConfigScript; - void Create(FxAstBlock* root_block); - - void Visit(FxAstNode* node); - - void Interpret(); - - FxScriptValue VisitExternalCall(FxAstActionCall* call, FxScriptExternalFunc& func); - FxScriptValue VisitActionCall(FxAstActionCall* call); - void VisitAssignment(FxAstAssign* assign); - FxScriptValue VisitRhs(FxAstNode* node); - - bool CheckExternalCallArgs(FxAstActionCall* call, FxScriptExternalFunc& func); - - - -private: - FxAstNode* mRootBlock = nullptr; - - bool mInCommandMode = false; - - std::vector mExternalFuncs; - - FxMPPagedArray mScopes; - FxScriptScope* mCurrentScope = nullptr; -}; - - -///////////////////////////////////// -// Bytecode to x86 Transpiler -///////////////////////////////////// - - -class FxScriptTranspilerX86 -{ -public: - FxScriptTranspilerX86(FxMPPagedArray& bytecode) - { - mBytecode = bytecode; - mBytecode.DoNotDestroy = true; - } - - void Print(); - void PrintOp(); - - -private: - uint16 Read16(); - uint32 Read32(); - - void DoPush(char* s, uint8 op_base, uint8 op_spec); - void DoPop(char* s, uint8 op_base, uint8 op_spec); - void DoLoad(char* s, uint8 op_base, uint8 op_spec); - void DoArith(char* s, uint8 op_base, uint8 op_spec); - void DoSave(char* s, uint8 op_base, uint8 op_spec); - void DoJump(char* s, uint8 op_base, uint8 op_spec); - void DoData(char* s, uint8 op_base, uint8 op_spec); - void DoType(char* s, uint8 op_base, uint8 op_spec); - void DoMove(char* s, uint8 op_base, uint8 op_spec); - - - void StrOut(const char* fmt, ...); - -private: - uint32 mBytecodeIndex = 0; - - uint32 mSizePushedInAction = 0; - bool mIsInAction = false; - - int mTextIndent = 0; - - FxMPPagedArray mBytecode; -}; diff --git a/FxScriptBytecode.hpp b/FxScriptBytecode.hpp deleted file mode 100644 index f96df0b..0000000 --- a/FxScriptBytecode.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include "FxScriptUtil.hpp" - - -/* -[BASE] [SPEC] -*/ - -enum OpBase : uint8 -{ - OpBase_Push = 1, - OpBase_Pop, - OpBase_Load, - OpBase_Arith, - OpBase_Save, - OpBase_Jump, - OpBase_Data, - OpBase_Type, - OpBase_Move, -}; - -enum OpSpecPush : uint8 -{ - OpSpecPush_Int32 = 1, // PUSH32 [imm] - OpSpecPush_Reg32, // PUSH32r [%r32] -}; - -enum OpSpecPop : uint8 -{ - OpSpecPop_Int32 = 1, // POP32 [%r32] -}; - -enum OpSpecLoad : uint8 -{ - OpSpecLoad_Int32 = 1, // LOAD32 [offset] [%r32] - OpSpecLoad_AbsoluteInt32, -}; - -enum OpSpecArith : uint8 -{ - OpSpecArith_Add = 1 // ADD [%r32] [%r32] -}; - -enum OpSpecSave : uint8 -{ - OpSpecSave_Int32 = 1, - OpSpecSave_Reg32, - OpSpecSave_AbsoluteInt32, - OpSpecSave_AbsoluteReg32 -}; - -enum OpSpecJump : uint8 -{ - OpSpecJump_Relative = 1, - OpSpecJump_Absolute, - OpSpecJump_AbsoluteReg32, - - OpSpecJump_CallAbsolute, - OpSpecJump_ReturnToCaller, - - OpSpecJump_CallExternal, -}; - -enum OpSpecData : uint8 -{ - OpSpecData_String = 1, - OpSpecData_ParamsStart, -}; - -enum OpSpecType : uint8 -{ - OpSpecType_Int = 1, - OpSpecType_String, -}; - -enum OpSpecMove : uint8 -{ - OpSpecMove_Int32 = 1, -}; diff --git a/Main.cpp b/Main.cpp index 4254ed9..dad66fe 100644 --- a/Main.cpp +++ b/Main.cpp @@ -1,4 +1,4 @@ -#include "FxScript.hpp" +#include "FoxScript.hpp" #include @@ -7,12 +7,12 @@ int main() { - FxConfigScript script; - script.LoadFile("Main.fxS"); + FoxConfigScript script; + script.LoadFile("Main.fox"); - script.DefineExternalVar("playerid", "emd22", FxScriptValue(FxScriptValue::INT, 1020)); + script.DefineExternalVar("playerid", "emd22", FoxValue(FoxValue::INT, 1020)); - FxScriptVM vm; + FoxVM vm; script.Execute(vm); diff --git a/Main.fox b/Main.fox new file mode 100644 index 0000000..4da2949 --- /dev/null +++ b/Main.fox @@ -0,0 +1,9 @@ +fn Get5() int +{ + return 5; +} + +local int x = Get5(); +local int y = x + 2; + +return y; diff --git a/Main.fxS b/Main.fxS deleted file mode 100644 index 245ace3..0000000 --- a/Main.fxS +++ /dev/null @@ -1,17 +0,0 @@ - -local int x = 5; -local int y = 2 + x; - - - - - - - - - -// push32 5 # Offset: 0 -// move32 X0, 2 # Offset: 6 -// load32 -4, X1 # Offset: 12 -// add32 X0, X1 # Offset: 16 -// push32r XR # Offset: 20 diff --git a/Makefile b/Makefile index 84507c8..0e7038d 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ LINKFLAGS := -lc++ BUILD_DIR := build -SRC := FxScript.cpp Main.cpp +SRC := FoxScript.cpp Main.cpp OBJ := $(SRC:%.cpp=$(BUILD_DIR)/%.o) DEP := $(OBJ:.o=.d) # dependency files TARGET := fxscript diff --git a/Test b/Test new file mode 100755 index 0000000..07fbb52 Binary files /dev/null and b/Test differ diff --git a/Test.asm b/Test.asm new file mode 100644 index 0000000..18d9e47 --- /dev/null +++ b/Test.asm @@ -0,0 +1,27 @@ +.global _main + +_R_0: + // Offset: 0 +mov w0, #5 +ret // Offset: 2 +// End of frame // Offset: 8 +// salloc 4 // Offset: 10 +// salloc 4 // Offset: 14 +// Entry point // Offset: 18 +// Branches // Offset: 20 +_main: +sub sp, sp, #32 +stp x29, x30, [sp, #16] +add x29, sp, #16 // Offset: 22 +// params begin // Offset: 24 +bl _R_0 // Offset: 26 +str w0, [sp, #12] // Offset: 32 +ldr w8, [sp, #12] // Offset: 38 +mov w9, #2 // Offset: 44 +add w8, w8, w9 // Offset: 50 +str w8, [sp, #8] // Offset: 54 +ldr w0, [sp, #8] // Offset: 60 +ldp x29, x30, [sp, #16] +add sp, sp, #32 +ret // Offset: 66 +// End of frame // Offset: 70 diff --git a/Test.c b/Test.c new file mode 100644 index 0000000..cd8af09 --- /dev/null +++ b/Test.c @@ -0,0 +1,17 @@ +int Get2() +{ + return 2; +} + +int Get5() +{ + return Get2() + 5; +} + + +int main() +{ + int x = Get5(); + int y = x + 2; + return y; +} diff --git a/Test.s b/Test.s new file mode 100644 index 0000000..c90a6a8 --- /dev/null +++ b/Test.s @@ -0,0 +1,51 @@ + .section __TEXT,__text,regular,pure_instructions + .build_version macos, 15, 0 sdk_version 15, 5 + .globl _Get2 ; -- Begin function Get2 + .p2align 2 +_Get2: ; @Get2 + .cfi_startproc +; %bb.0: + mov w0, #2 ; =0x2 + ret + .cfi_endproc + ; -- End function + .globl _Get5 ; -- Begin function Get5 + .p2align 2 +_Get5: ; @Get5 + .cfi_startproc +; %bb.0: + stp x29, x30, [sp, #-16]! ; 16-byte Folded Spill + mov x29, sp + .cfi_def_cfa w29, 16 + .cfi_offset w30, -8 + .cfi_offset w29, -16 + bl _Get2 + add w0, w0, #5 + ldp x29, x30, [sp], #16 ; 16-byte Folded Reload + ret + .cfi_endproc + ; -- End function + .globl _main ; -- Begin function main + .p2align 2 +_main: ; @main + .cfi_startproc +; %bb.0: + sub sp, sp, #32 + stp x29, x30, [sp, #16] ; 16-byte Folded Spill + add x29, sp, #16 + .cfi_def_cfa w29, 16 + .cfi_offset w30, -8 + .cfi_offset w29, -16 + stur wzr, [x29, #-4] + bl _Get5 + str w0, [sp, #8] + ldr w8, [sp, #8] + add w8, w8, #2 + str w8, [sp, #4] + ldr w0, [sp, #4] + ldp x29, x30, [sp, #16] ; 16-byte Folded Reload + add sp, sp, #32 + ret + .cfi_endproc + ; -- End function +.subsections_via_symbols diff --git a/Test1 b/Test1 new file mode 100644 index 0000000..44b36dd --- /dev/null +++ b/Test1 @@ -0,0 +1,44 @@ + .section __TEXT,__text,regular,pure_instructions + .build_version macos, 15, 0 sdk_version 15, 5 + .globl _some_function ; -- Begin function some_function + .p2align 2 +_some_function: ; @some_function + .cfi_startproc +; %bb.0: + sub sp, sp, #16 + .cfi_def_cfa_offset 16 + str w0, [sp, #12] + ldr w8, [sp, #12] + add w0, w8, #5 + add sp, sp, #16 + ret + .cfi_endproc + ; -- End function + .globl _main ; -- Begin function main + .p2align 2 +_main: ; @main + .cfi_startproc +; %bb.0: + sub sp, sp, #32 + stp x29, x30, [sp, #16] ; 16-byte Folded Spill + add x29, sp, #16 + .cfi_def_cfa w29, 16 + .cfi_offset w30, -8 + .cfi_offset w29, -16 + mov w8, #0 ; =0x0 + str w8, [sp] ; 4-byte Folded Spill + stur wzr, [x29, #-4] + mov w0, #10 ; =0xa + bl _some_function + mov x8, x0 + ldr w0, [sp] ; 4-byte Folded Reload + str w8, [sp, #8] + ldr w8, [sp, #8] + add w8, w8, #2 + str w8, [sp, #4] + ldp x29, x30, [sp, #16] ; 16-byte Folded Reload + add sp, sp, #32 + ret + .cfi_endproc + ; -- End function +.subsections_via_symbols diff --git a/a.out b/a.out new file mode 100755 index 0000000..efa2bc2 Binary files /dev/null and b/a.out differ