Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
02227b1
feat: add support for Ovum JIT X64 architecture in CMake configuration
bialger Jan 6, 2026
b2bb021
feat: enable JIT support in vm_ui CMake configuration and link jit li…
bialger Jan 6, 2026
71c18cf
fix: update help message in StartVmConsoleUI to include description o…
bialger Jan 6, 2026
538e0d2
refactor: extract help message into a constant for improved readabili…
bialger Jan 6, 2026
725ccdc
refactor: added jit_body copiing and translation to JITExecutorFactory
sashbek Jan 9, 2026
82b454d
refactor: fixed uniqueptr usage to shared ptr, CopyUntilBlockEnd()
sashbek Jan 9, 2026
4711816
fix: CopyUntilBlockEnd
sashbek Jan 10, 2026
ab986f7
config: add jit library
sashbek Jan 11, 2026
6d42246
Merge branch 'master' into jit_integration
bialger Jan 11, 2026
6bb0539
build: conditionally link JIT library in executor CMake configuration
bialger Jan 11, 2026
4dbad7c
refactor: improve code formatting and consistency in function signatu…
bialger Jan 11, 2026
6be4d25
feat: add copy assignment methods for fundamental types and arrays, i…
bialger Jan 11, 2026
bdfe350
fix: non const IJITExecutor::TryCompile
sashbek Jan 13, 2026
a76ca06
merge: solved conflict with div branches
sashbek Jan 13, 2026
29e2ddf
fix: correct argument extraction order in FormatDateTime and enhance …
bialger Jan 14, 2026
44dbe73
fix: improve FloatToString precision handling by using stringstream f…
bialger Jan 14, 2026
b36c192
fix: correct float string conversion to match expected precision in t…
bialger Jan 14, 2026
89d335a
fix: stack frame for jit executable
sashbek Jan 15, 2026
801c8e1
tests: two tests for jit to compare time
sashbek Jan 15, 2026
4455e3a
refactor: clean up whitespace in JIT execution logic and improve test…
bialger Jan 15, 2026
f846d92
docs: update README to clarify JIT compiler availability for x86_64 a…
bialger Jan 15, 2026
3834e25
docs: add link to OvumExamples repository in README for additional re…
bialger Jan 15, 2026
97d7b18
fix: ensure sufficient arguments on stack for JIT-compiled function c…
bialger Jan 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The VM consists of several core components:
- **System Interface**: Provides access to system functions and FFI

> **Note**: Due to the architecture-dependent nature of JIT compilation, JIT implementations are located in separate repositories for each target architecture.
> **Note**: The JIT compiler is currently only available for x86_64 architecture.

## Quick Start

Expand Down Expand Up @@ -112,7 +113,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.

- **[Ovum Language](https://github.com/Ovum-Programming-Language/OvumLanguage)**: The main Ovum programming language repository
- **[Ovum Documentation](https://ovum-programming-language.github.io/OvumDocs/)**: Complete language and VM documentation
- **JIT Implementations**: Architecture-specific JIT compilers (separate repositories)
- **[Ovum JIT X64](https://github.com/Ovum-Programming-Language/OvumJitX64)**: JIT compiler for x86_64 architecture
- **[OvumExamples](https://github.com/Ovum-Programming-Language/OvumExamples)**: Examples for the Ovum programming language and Ovum Intermediate Language

## Language Features Supported

Expand Down
15 changes: 15 additions & 0 deletions cmake/IncludeExternalLibraries.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,18 @@ FetchContent_Declare(
)

FetchContent_MakeAvailable(argparser)


# Ovum JIT X64 (only for x86_64 architecture)
if(OVUM_JIT_X64_AVAILABLE)
FetchContent_Declare(
ovumjitx64
GIT_REPOSITORY https://github.com/Ovum-Programming-Language/OvumJitX64.git
GIT_TAG main
)

FetchContent_MakeAvailable(ovumjitx64)
message(STATUS "OvumJitX64: Enabled for x86_64 architecture")
else()
message(STATUS "OvumJitX64: Skipped (not x86_64 architecture)")
endif()
12 changes: 12 additions & 0 deletions cmake/SetCompilerOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ else()
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
endif()

# Detect target architecture for JIT support
# x86_64 is reported as "AMD64" on Windows, "x86_64" on Linux/Mac
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|x86_64)$")
set(OVUM_JIT_X64_AVAILABLE ON CACHE BOOL "JIT compiler available for x86_64 architecture")
set(JIT_PROVIDED ON CACHE BOOL "JIT compiler provided")
add_compile_definitions(JIT_PROVIDED)
message(STATUS "Architecture: ${CMAKE_SYSTEM_PROCESSOR} - JIT support available")
else()
set(OVUM_JIT_X64_AVAILABLE OFF CACHE BOOL "JIT compiler available for x86_64 architecture")
message(STATUS "Architecture: ${CMAKE_SYSTEM_PROCESSOR} - JIT support not available")
endif()

message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "Compiler version: ${CMAKE_CXX_COMPILER_VERSION}")
Expand Down
21 changes: 21 additions & 0 deletions lib/bytecode_parser/ParsingSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,25 @@ std::expected<bool, BytecodeParserError> ParsingSession::ConsumeBoolLiteral() {
std::to_string(token->GetPosition().GetColumn())));
}

std::vector<TokenPtr> ParsingSession::CopyUntilBlockEnd() {
std::vector<TokenPtr> result;
size_t pos = pos_;
size_t cnt = 1;
while (pos < tokens_.size() && tokens_[pos]->GetStringType() != "EOF") {
if (tokens_[pos]->GetStringType() == "PUNCT" && tokens_[pos]->GetLexeme() == std::string(1, '}')) {
--cnt;
if (cnt == 0) {
break;
}
} else if (tokens_[pos]->GetStringType() == "PUNCT" && tokens_[pos]->GetLexeme() == std::string(1, '{')) {
++cnt;
}

result.push_back(tokens_[pos]);
++pos;
}

return result;
}

} // namespace ovum::bytecode::parser
2 changes: 2 additions & 0 deletions lib/bytecode_parser/ParsingSession.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class ParsingSession {
std::unique_ptr<vm::execution_tree::Block> GetInitStaticBlock();
void SetInitStaticBlock(std::unique_ptr<vm::execution_tree::Block> block);

std::vector<TokenPtr> CopyUntilBlockEnd();

private:
const std::vector<TokenPtr>& tokens_;
size_t pos_ = 0;
Expand Down
11 changes: 6 additions & 5 deletions lib/bytecode_parser/scenarios/FunctionFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ vm::execution_tree::PureFunction<Base> FunctionFactory::WrapPure(Base&& base,

template<vm::execution_tree::ExecutableFunction Base>
std::expected<vm::execution_tree::JitCompilingFunction<Base>, std::runtime_error> FunctionFactory::WrapJit(
Base&& base) {
Base&& base, std::shared_ptr<std::vector<TokenPtr>> jit_body) {
if (!jit_factory_.has_value()) {
return std::unexpected(std::runtime_error("Jit factory not set"));
}

return {vm::execution_tree::JitCompilingFunction<Base>(
jit_factory_->get().Create(base.GetId()), std::forward<Base>(base), jit_boundary_)};
jit_factory_->get().Create(base.GetId(), std::move(jit_body)), std::forward<Base>(base), jit_boundary_)};
}

std::unique_ptr<vm::execution_tree::IFunctionExecutable> FunctionFactory::Create(
Expand All @@ -35,15 +35,16 @@ std::unique_ptr<vm::execution_tree::IFunctionExecutable> FunctionFactory::Create
std::unique_ptr<vm::execution_tree::Block> body,
bool pure,
std::vector<std::string> pure_argument_types,
bool no_jit) {
bool no_jit,
std::shared_ptr<std::vector<TokenPtr>> jit_body) {
RegularFunction regular = MakeRegular(id, arity, std::move(body));

if (!pure || pure_argument_types.empty()) {
if (no_jit || !jit_factory_.has_value()) {
return std::make_unique<RegularFunction>(std::move(regular));
}

std::expected<JitFunction, std::runtime_error> jit_func = WrapJit(std::move(regular));
std::expected<JitFunction, std::runtime_error> jit_func = WrapJit(std::move(regular), std::move(jit_body));

if (!jit_func) {
return nullptr;
Expand All @@ -56,7 +57,7 @@ std::unique_ptr<vm::execution_tree::IFunctionExecutable> FunctionFactory::Create
return std::make_unique<PureFunction>(WrapPure(std::move(regular), std::move(pure_argument_types)));
}

std::expected<JitFunction, std::runtime_error> jit_func = WrapJit(std::move(regular));
std::expected<JitFunction, std::runtime_error> jit_func = WrapJit(std::move(regular), std::move(jit_body));

if (!jit_func) {
return nullptr;
Expand Down
19 changes: 12 additions & 7 deletions lib/bytecode_parser/scenarios/FunctionFactory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <string>
#include <vector>

#include <tokens/Token.hpp>

#include "lib/execution_tree/Block.hpp"
#include "lib/execution_tree/Function.hpp"
#include "lib/execution_tree/IFunctionExecutable.hpp"
Expand All @@ -28,12 +30,14 @@ class FunctionFactory {
FunctionFactory(std::optional<std::reference_wrapper<vm::executor::IJitExecutorFactory>> jit_factory,
size_t jit_boundary);

std::unique_ptr<vm::execution_tree::IFunctionExecutable> Create(const vm::runtime::FunctionId& id,
size_t arity,
std::unique_ptr<vm::execution_tree::Block> body,
bool pure = false,
std::vector<std::string> pure_argument_types = {},
bool no_jit = false);
std::unique_ptr<vm::execution_tree::IFunctionExecutable> Create(
const vm::runtime::FunctionId& id,
size_t arity,
std::unique_ptr<vm::execution_tree::Block> body,
bool pure = false,
std::vector<std::string> pure_argument_types = {},
bool no_jit = false,
std::shared_ptr<std::vector<TokenPtr>> jit_body = nullptr);

private:
vm::execution_tree::Function MakeRegular(const vm::runtime::FunctionId& id,
Expand All @@ -44,7 +48,8 @@ class FunctionFactory {
vm::execution_tree::PureFunction<Base> WrapPure(Base&& base, std::vector<std::string>&& argument_types);

template<vm::execution_tree::ExecutableFunction Base>
std::expected<vm::execution_tree::JitCompilingFunction<Base>, std::runtime_error> WrapJit(Base&& base);
std::expected<vm::execution_tree::JitCompilingFunction<Base>, std::runtime_error> WrapJit(
Base&& base, std::shared_ptr<std::vector<TokenPtr>> jit_body);

std::optional<std::reference_wrapper<vm::executor::IJitExecutorFactory>> jit_factory_;
size_t jit_boundary_;
Expand Down
10 changes: 8 additions & 2 deletions lib/bytecode_parser/scenarios/FunctionParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ std::expected<bool, BytecodeParserError> FunctionParser::Handle(ParsingSessionPt

std::unique_ptr<vm::execution_tree::Block> body = std::make_unique<vm::execution_tree::Block>();

std::shared_ptr<std::vector<TokenPtr>> jit_body;
if (!no_jit) {
auto jit_body_vec = ctx->CopyUntilBlockEnd();
jit_body = std::make_shared<std::vector<TokenPtr>>(jit_body_vec);
}

ctx->SetCurrentBlock(body.get());

while (!ctx->IsPunct('}') && !ctx->IsEof()) {
Expand All @@ -112,8 +118,8 @@ std::expected<bool, BytecodeParserError> FunctionParser::Handle(ParsingSessionPt

FunctionFactory factory(ctx->GetJitFactory(), ctx->GetJitBoundary());

std::unique_ptr<vm::execution_tree::IFunctionExecutable> func =
factory.Create(name_res.value(), arity, std::move(body), is_pure, std::move(pure_types), no_jit);
std::unique_ptr<vm::execution_tree::IFunctionExecutable> func = factory.Create(
name_res.value(), arity, std::move(body), is_pure, std::move(pure_types), no_jit, std::move(jit_body));

if (func == nullptr) {
return std::unexpected(BytecodeParserError("Failed to create function: JIT compilation failed"));
Expand Down
14 changes: 10 additions & 4 deletions lib/execution_tree/BytecodeCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <cmath>
#include <ctime>
#include <filesystem>
#include <limits>
#include <random>
#include <ranges>
#include <sstream>
Expand Down Expand Up @@ -1006,7 +1007,10 @@ std::expected<ExecutionResult, std::runtime_error> FloatToString(PassedExecution
return std::unexpected(argument.error());
}

return PushString(data, std::to_string(argument.value()));
std::ostringstream oss;
oss << std::setprecision(std::numeric_limits<double>::max_digits10 - 2) << argument.value();

return PushString(data, oss.str());
}

std::expected<ExecutionResult, std::runtime_error> IntToFloat(PassedExecutionData& data) {
Expand Down Expand Up @@ -1201,6 +1205,8 @@ std::expected<ExecutionResult, std::runtime_error> CallConstructor(PassedExecuti
if (first_underscore != std::string::npos && second_underscore != std::string::npos &&
second_underscore > first_underscore + 1) {
class_name = constructor_name.substr(first_underscore + 1, second_underscore - first_underscore - 1);
} else if (first_underscore != std::string::npos) {
class_name = constructor_name.substr(first_underscore + 1);
} else {
class_name = constructor_name;
}
Expand Down Expand Up @@ -1575,15 +1581,15 @@ std::expected<ExecutionResult, std::runtime_error> NanoTime(PassedExecutionData&
}

std::expected<ExecutionResult, std::runtime_error> FormatDateTime(PassedExecutionData& data) {
auto arguments = TryExtractTwoArguments<void*, int64_t>(data, "FormatDateTime");
auto arguments = TryExtractTwoArguments<int64_t, void*>(data, "FormatDateTime");

if (!arguments) {
return std::unexpected(arguments.error());
}

auto timestamp_var = arguments.value().second;
auto timestamp_var = arguments.value().first;

void* string_obj1 = arguments.value().first;
void* string_obj1 = arguments.value().second;
auto* format_str_ptr = runtime::GetDataPointer<std::string>(string_obj1);

try {
Expand Down
25 changes: 24 additions & 1 deletion lib/execution_tree/JitCompilingFunction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,34 @@ class JitCompilingFunction : public IFunctionExecutable {
std::expected<ExecutionResult, std::runtime_error> Execute(PassedExecutionData& execution_data) override {
if (function_.GetTotalActionCount() > jit_action_boundary_) {
if (executor_->TryCompile()) {
const std::expected<void, std::runtime_error> jit_result = executor_->Run(execution_data.memory.machine_stack);
if (execution_data.memory.machine_stack.size() < function_.GetArity()) {
return std::unexpected<std::runtime_error>(std::runtime_error(
"Not enough arguments on the stack to call JIT-compiled function " + function_.GetId()));
}

runtime::StackFrame local_frame{};
local_frame.function_name = function_.GetId();
local_frame.local_variables.reserve(function_.GetArity());

for (size_t i = 0; i < function_.GetArity(); ++i) {
local_frame.local_variables.emplace_back(execution_data.memory.machine_stack.top());
execution_data.memory.machine_stack.pop();
}
Comment thread
bialger marked this conversation as resolved.

execution_data.memory.stack_frames.push(std::move(local_frame));

const std::expected<void, std::runtime_error> jit_result = executor_->Run(execution_data);
const runtime::StackFrame& local_frame_ref = execution_data.memory.stack_frames.top();

execution_data.memory.stack_frames.pop();
Comment thread
bialger marked this conversation as resolved.

if (jit_result.has_value()) {
return ExecutionResult::kNormal;
}

for (size_t i = 0; i < function_.GetArity(); ++i) {
execution_data.memory.machine_stack.push(local_frame_ref.local_variables[function_.GetArity() - i - 1]);
}
}
}

Expand Down
Loading