Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bootstrap Classloader #35

Merged
merged 4 commits into from
May 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 27 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ project(SchokoVM)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Java 1.8 REQUIRED)
find_package(Java 11 EXACT REQUIRED COMPONENTS Runtime Development)
find_package(JNI 11 EXACT REQUIRED)
include(UseJava)

find_package(libzip)
Expand Down Expand Up @@ -102,6 +103,31 @@ else ()
target_link_libraries(SchokoVM zip)
endif ()


#
# OpenJDK
#

file(REAL_PATH ${Java_JAVAC_EXECUTABLE} JDK_HOME)
get_filename_component(JDK_HOME ${JDK_HOME} DIRECTORY)
get_filename_component(JDK_HOME ${JDK_HOME} DIRECTORY)
message(STATUS "jdk: ${JDK_HOME}")

set(JDK_BIN ${CMAKE_CURRENT_SOURCE_DIR}/jdk/bin)
if(NOT IS_DIRECTORY ${JDK_BIN})
message(STATUS "Copying ${JDK_HOME} to ${JDK_BIN}")
# file(COPY ${JDK_HOME} DESTINATION ${JDK_BIN})
# this renames the target folder:
execute_process(COMMAND cp -r "${JDK_HOME}" "${JDK_BIN}")
endif()

set(JDK_EXPLODED ${CMAKE_CURRENT_SOURCE_DIR}/jdk/exploded-modules)
if(NOT IS_DIRECTORY ${JDK_EXPLODED})
message(STATUS "Exploding")
execute_process(COMMAND ${JDK_HOME}/bin/jimage extract --dir "${JDK_EXPLODED}" "${JDK_BIN}/lib/modules")
endif()


#
# Tests
#
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ A Java Bytecode interpreter.

[![Run tests](https://github.com/RobertObkircher/SchokoVM/actions/workflows/tests.yml/badge.svg)](https://github.com/RobertObkircher/SchokoVM/actions/workflows/tests.yml)

# OpenJDK

You need to download an OpenJDK release and the sources: `cd jdk && ./download.sh`

# GDB

You need a ~/.gdbinit in your home directory or the project .gdbinit file will be ignored:
Expand Down
3 changes: 3 additions & 0 deletions jdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/bin/
/exploded-modules/
/jdk/
6 changes: 6 additions & 0 deletions jdk/cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
set -e

# clion doesn't want to delete the symlinks
rm -rf bin
rm -rf exploded-modules
13 changes: 13 additions & 0 deletions jdk/download.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
set -e

if [ -d "jdk" ]; then
echo "Skipping download of jdk sources"
else
git clone https://github.com/openjdk/jdk.git
pushd jdk
git checkout -b SchokoVM jdk-11+9
popd
mischnic marked this conversation as resolved.
Show resolved Hide resolved
fi


14 changes: 10 additions & 4 deletions src/classfile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,9 @@ struct ClassFile {
u2 access_flags;
CONSTANT_Class_info *this_class;
// nullptr for class Object
CONSTANT_Class_info *super_class;
CONSTANT_Class_info *super_class_ref;
// nullptr for class Object and before resolution
ClassFile *super_class;
std::vector<CONSTANT_Class_info *> interfaces;
std::vector<field_info> fields;
std::vector<method_info> methods;
Expand All @@ -799,17 +801,19 @@ struct ClassFile {
// Computes whether `this` is a subclass of `other` (regarding both `extends` and `implements`).
// Note that `x.is_subclass_of(x) == false`
bool is_subclass_of(ClassFile *other) {
if (this->super_class == other) {
return true;
}
for (auto &i: interfaces) {
if (i->clazz == other) {
return true;
}
}
if (this->super_class != nullptr &&
(this->super_class->clazz == other || this->super_class->clazz->is_subclass_of(other))) {
if (this->super_class != nullptr && this->super_class->is_subclass_of(other)) {
return true;
}
for (auto &i: interfaces) {
if (i->clazz == other || i->clazz->is_subclass_of(other)) {
if (i->clazz->is_subclass_of(other)) {
return true;
}
}
Expand All @@ -819,6 +823,8 @@ struct ClassFile {
[[nodiscard]] inline bool is_interface() const {
return (access_flags & static_cast<u2>(ClassFileAccessFlags::ACC_INTERFACE)) != 0;
}

[[nodiscard]] inline std::string const &name() const { return this_class->name->value; }
};


Expand Down
96 changes: 83 additions & 13 deletions src/classloading.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,76 @@
#include <filesystem>
#include <utility>

#include "classloading.hpp"
#include "parser.hpp"
#include "util.hpp"
#include "zip.hpp"

BootstrapClassLoader::BootstrapClassLoader(const std::string &bootclasspath) : class_path_entries(), buffer() {
// TODO fix hardcoded path
class_path_entries.push_back({"java.base", {}, "../jdk/exploded-modules/java.base", {}});

for (auto &path : split(bootclasspath, ':')) {
if (path.ends_with(".jar") || path.ends_with(".zip")) {
class_path_entries.push_back({"", {}, "", ZipArchive(path)});
} else {
class_path_entries.push_back({path, {}, path, {}});
}
}
}

// https://stackoverflow.com/a/7782037
struct membuf : std::streambuf {
membuf(char *begin, char *end) {
this->setg(begin, begin, end);
}
};

ClassFile *BootstrapClassLoader::load(std::string const &name) {
for (auto &cp_entry : class_path_entries) {
if (auto loaded = cp_entry.class_files.find(name); loaded != cp_entry.class_files.end()) {
if (loaded->second == nullptr) {
continue;
}
return loaded->second.get();
}

std::unique_ptr<ClassFile> parsed{};

if (!cp_entry.directory.empty()) {
auto path = cp_entry.directory + "/" + name + ".class";
std::ifstream in{path, std::ios::in | std::ios::binary};

if (in) {
Parser parser{in};
parsed = std::make_unique<ClassFile>(parser.parse());
}
} else if (!cp_entry.zip.path.empty()) {
auto path = name + ".class";

if (ZipEntry const *zip_entry = cp_entry.zip.entry_for_path(path); zip_entry != nullptr) {
cp_entry.zip.read(*zip_entry, buffer);

membuf buf{buffer.data(), buffer.data() + buffer.size()};
std::istream in{&buf};

Parser parser{in};
parsed = std::make_unique<ClassFile>(parser.parse());
}
}

ClassFile *result = parsed.get();
cp_entry.class_files.insert({name, std::move(parsed)});

if (result != nullptr) {
if (name != result->name()) {
throw ParseError("unexpected name");
}
return result;
}
}
return nullptr;
}

static bool initialize_class(ClassFile *clazz, Thread &thread, Frame &frame) {
for (const auto &field : clazz->fields) {
Expand Down Expand Up @@ -52,7 +124,7 @@ static bool initialize_class(ClassFile *clazz, Thread &thread, Frame &frame) {
}


bool resolve_class(std::unordered_map<std::string_view, ClassFile *> &class_files, CONSTANT_Class_info *class_info,
bool resolve_class(BootstrapClassLoader &bootstrap_class_loader, CONSTANT_Class_info *class_info,
Thread &thread, Frame &frame) {
if (class_info->clazz == nullptr) {
auto &name = class_info->name->value;
Expand All @@ -62,12 +134,11 @@ bool resolve_class(std::unordered_map<std::string_view, ClassFile *> &class_file

// TODO we also need to deal with array classes here. They also load the class for the elements.

auto result = class_files.find(name);
if (result == class_files.end()) {
ClassFile *clazz = bootstrap_class_loader.load(name);
if (clazz == nullptr) {
// TODO this prints "A not found" if A was found but a superclass/interface wasn't
throw std::runtime_error("class not found: '" + name + "'");
}
ClassFile *clazz = result->second;

if (clazz->resolved) {
class_info->clazz = clazz;
Expand All @@ -80,13 +151,12 @@ bool resolve_class(std::unordered_map<std::string_view, ClassFile *> &class_file
}

size_t parent_instance_field_count = 0;
// TODO use stdlib
if (clazz->super_class != nullptr && clazz->super_class->name->value != "java/lang/Object" &&
clazz->super_class->name->value != "java/lang/Exception") {
auto runInitializer = resolve_class(class_files, clazz->super_class, thread, frame);
if (runInitializer)
return runInitializer;
parent_instance_field_count = clazz->super_class->clazz->total_instance_field_count;
if (clazz->super_class == nullptr && clazz->super_class_ref != nullptr) {
if (resolve_class(bootstrap_class_loader, clazz->super_class_ref, thread, frame))
return true;

clazz->super_class = clazz->super_class_ref->clazz;
parent_instance_field_count = clazz->super_class->total_instance_field_count;
}

// instance and static fields
Expand All @@ -105,7 +175,7 @@ bool resolve_class(std::unordered_map<std::string_view, ClassFile *> &class_file
clazz->static_field_values.resize(clazz->fields.size() - clazz->declared_instance_field_count);

for (auto &interface : clazz->interfaces) {
auto runInitializer = resolve_class(class_files, interface, thread, frame);
auto runInitializer = resolve_class(bootstrap_class_loader, interface, thread, frame);
if (runInitializer)
return true;
}
Expand Down Expand Up @@ -150,7 +220,7 @@ bool resolve_field_recursive(ClassFile *clazz, CONSTANT_Fieldref_info *field_inf

if (clazz->super_class != nullptr) {
// 3.
clazz = clazz->super_class->clazz;
clazz = clazz->super_class;
} else {
break;
}
Expand Down
25 changes: 24 additions & 1 deletion src/classloading.hpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
#ifndef SCHOKOVM_CLASSLOADING_HPP
#define SCHOKOVM_CLASSLOADING_HPP

#include <optional>
#include <unordered_map>

#include "classfile.hpp"
#include "interpreter.hpp"
#include "zip.hpp"

struct ClassPathEntry {
std::string module_name;
std::unordered_map<std::string, std::unique_ptr<ClassFile>> class_files;

// TODO use subtyping instead?
// TODO should module entries refer to other entries?
// one of those will be empty
std::string directory;
ZipArchive zip;
};

struct BootstrapClassLoader {
std::vector<ClassPathEntry> class_path_entries;
std::vector<char> buffer;

explicit BootstrapClassLoader(const std::string &bootclasspath);

ClassFile *load(std::string const &name);
};

/**
* Throws if the class was not found
* @return whether a stack frame for an initializer was pushed
*/
bool resolve_class(std::unordered_map<std::string_view, ClassFile *> &class_files, CONSTANT_Class_info *class_info,
bool resolve_class(BootstrapClassLoader &bootstrap_class_loader, CONSTANT_Class_info *class_info,
Thread &thread, Frame &frame);

bool resolve_field_recursive(ClassFile *clazz, CONSTANT_Fieldref_info *field_info);
Expand Down
Loading