Skip to content

Commit

Permalink
Merge pull request #129 from libriscv/various
Browse files Browse the repository at this point in the history
Various
  • Loading branch information
fwsGonzo authored Sep 19, 2024
2 parents f6e7fce + 9f4de4d commit 9a9474c
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 141 deletions.
2 changes: 1 addition & 1 deletion program/cpp/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ FROM ubuntu:24.04
# Install dependencies
RUN apt-get update && apt-get install -y \
g++-14-riscv64-linux-gnu \
git cmake mold
git cmake mold ccache

# Enter the shared directory
WORKDIR /usr/src
Expand Down
6 changes: 4 additions & 2 deletions program/cpp/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ for file in $@ $API/*.cpp; do
if [ "$locally" = true ]; then
riscv64-unknown-elf-g++ $CPPFLAGS -I$API -c $file -o $file.o &
else
riscv64-linux-gnu-g++-14 $CPPFLAGS -march=rv64gc_zba_zbb_zbs_zbc -mabi=lp64d -I$API -c $file -o $file.o &
export CXX="riscv64-linux-gnu-g++-14"
ccache $CXX $CPPFLAGS -march=rv64gc_zba_zbb_zbs_zbc -mabi=lp64d -I$API -c $file -o $file.o &
fi
done

Expand All @@ -62,6 +63,7 @@ wait
if [ "$locally" = true ]; then
riscv64-unknown-elf-g++ -static $CPPFLAGS $LINKEROPS $@.o $API/*.cpp.o -o $output
else
export CXX="riscv64-linux-gnu-g++-14"
LINKEROPS="$LINKEROPS -fuse-ld=mold"
riscv64-linux-gnu-g++-14 -static $CPPFLAGS -march=rv64gc_zba_zbb_zbs_zbc -mabi=lp64d $LINKEROPS $@.o $API/*.cpp.o -o $output
ccache $CXX -static $CPPFLAGS -march=rv64gc_zba_zbb_zbs_zbc -mabi=lp64d $LINKEROPS $@.o $API/*.cpp.o -o $output
fi
31 changes: 31 additions & 0 deletions src/guest_datatypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ struct GuestVariant {
**/
void create(Sandbox &emu, Variant &&value);

/**
* @brief Check if the GuestVariant is implemented using an index to a scoped Variant.
*
* @return true If the type of the GuestVariant is implemented using an index to a scoped Variant.
*/
bool is_scoped_variant() const noexcept;

Variant::Type type = Variant::NIL;
union alignas(8) {
int64_t i = 0;
Expand All @@ -275,4 +282,28 @@ struct GuestVariant {
void free(Sandbox &emu);
};

inline bool GuestVariant::is_scoped_variant() const noexcept {
switch (type) {
case Variant::STRING:
case Variant::DICTIONARY:
case Variant::ARRAY:
case Variant::CALLABLE:
case Variant::STRING_NAME:
case Variant::NODE_PATH:
case Variant::PACKED_BYTE_ARRAY:
case Variant::PACKED_FLOAT32_ARRAY:
case Variant::PACKED_FLOAT64_ARRAY:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY:
case Variant::PACKED_VECTOR2_ARRAY:
case Variant::PACKED_VECTOR3_ARRAY:
case Variant::PACKED_COLOR_ARRAY: {
return true;
}
case Variant::OBJECT: // Objects are raw pointers.
default:
return false;
}
}

static_assert(sizeof(GuestVariant) == 24, "GuestVariant size mismatch");
5 changes: 3 additions & 2 deletions src/guest_variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ Variant GuestVariant::toVariant(const Sandbox &emu) const {

const Variant *GuestVariant::toVariantPtr(const Sandbox &emu) const {
switch (type) {
case Variant::STRING:
case Variant::DICTIONARY:
case Variant::ARRAY:
case Variant::CALLABLE:
case Variant::STRING:
case Variant::STRING_NAME:
case Variant::NODE_PATH:
case Variant::PACKED_BYTE_ARRAY:
Expand All @@ -79,7 +79,8 @@ const Variant *GuestVariant::toVariantPtr(const Sandbox &emu) const {
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY:
case Variant::PACKED_VECTOR2_ARRAY:
case Variant::PACKED_VECTOR3_ARRAY: {
case Variant::PACKED_VECTOR3_ARRAY:
case Variant::PACKED_COLOR_ARRAY: {
if (std::optional<const Variant *> v = emu.get_scoped_variant(this->v.i))
return v.value();
else
Expand Down
65 changes: 45 additions & 20 deletions src/sandbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ void Sandbox::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_function", "function"), &Sandbox::has_function);

// Properties.
ClassDB::bind_method(D_METHOD("set_max_refs", "max"), &Sandbox::set_max_refs);
ClassDB::bind_method(D_METHOD("set_max_refs", "max"), &Sandbox::set_max_refs, DEFVAL(MAX_REFS));
ClassDB::bind_method(D_METHOD("get_max_refs"), &Sandbox::get_max_refs);
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_references", PROPERTY_HINT_NONE, "Maximum objects and variants referenced by a sandbox call"), "set_max_refs", "get_max_refs");

ClassDB::bind_method(D_METHOD("set_memory_max", "max"), &Sandbox::set_memory_max);
ClassDB::bind_method(D_METHOD("set_memory_max", "max"), &Sandbox::set_memory_max, DEFVAL(MAX_VMEM));
ClassDB::bind_method(D_METHOD("get_memory_max"), &Sandbox::get_memory_max);
ADD_PROPERTY(PropertyInfo(Variant::INT, "memory_max", PROPERTY_HINT_NONE, "Maximum memory (in MiB) used by the sandboxed program"), "set_memory_max", "get_memory_max");

ClassDB::bind_method(D_METHOD("set_instructions_max", "max"), &Sandbox::set_instructions_max);
ClassDB::bind_method(D_METHOD("set_instructions_max", "max"), &Sandbox::set_instructions_max, DEFVAL(MAX_INSTRUCTIONS));
ClassDB::bind_method(D_METHOD("get_instructions_max"), &Sandbox::get_instructions_max);
ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_timeout", PROPERTY_HINT_NONE, "Maximum billions of instructions executed before cancelling execution"), "set_instructions_max", "get_instructions_max");
ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_timeout", PROPERTY_HINT_NONE, "Maximum millions of instructions executed before cancelling execution"), "set_instructions_max", "get_instructions_max");

ClassDB::bind_method(D_METHOD("set_use_unboxed_arguments", "use_unboxed_arguments"), &Sandbox::set_use_unboxed_arguments);
ClassDB::bind_method(D_METHOD("get_use_unboxed_arguments"), &Sandbox::get_use_unboxed_arguments);
Expand All @@ -60,18 +60,21 @@ void Sandbox::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_heap_usage"), &Sandbox::get_heap_usage);
ADD_PROPERTY(PropertyInfo(Variant::INT, "monitor_heap_usage", PROPERTY_HINT_NONE, "Current arena usage"), "", "get_heap_usage");

ClassDB::bind_method(D_METHOD("get_budget_overruns"), &Sandbox::get_budget_overruns);
ADD_PROPERTY(PropertyInfo(Variant::INT, "monitor_execution_timeouts", PROPERTY_HINT_NONE, "Number of execution timeouts"), "", "get_budget_overruns");
ClassDB::bind_method(D_METHOD("get_exceptions"), &Sandbox::get_exceptions);
ADD_PROPERTY(PropertyInfo(Variant::INT, "monitor_exceptions", PROPERTY_HINT_NONE, "Number of exceptions thrown"), "", "get_exceptions");

ClassDB::bind_method(D_METHOD("get_timeouts"), &Sandbox::get_timeouts);
ADD_PROPERTY(PropertyInfo(Variant::INT, "monitor_execution_timeouts", PROPERTY_HINT_NONE, "Number of execution timeouts"), "", "get_timeouts");

ClassDB::bind_method(D_METHOD("get_calls_made"), &Sandbox::get_calls_made);
ADD_PROPERTY(PropertyInfo(Variant::INT, "monitor_calls_made", PROPERTY_HINT_NONE, "Number of calls made"), "", "get_calls_made");

ClassDB::bind_static_method("Sandbox", D_METHOD("get_global_calls_made"), &Sandbox::get_global_calls_made);
ClassDB::bind_static_method("Sandbox", D_METHOD("get_global_exceptions"), &Sandbox::get_global_exceptions);
ClassDB::bind_static_method("Sandbox", D_METHOD("get_global_budget_overruns"), &Sandbox::get_global_budget_overruns);
ClassDB::bind_static_method("Sandbox", D_METHOD("get_global_timeouts"), &Sandbox::get_global_timeouts);
ADD_PROPERTY(PropertyInfo(Variant::INT, "monitor_global_calls_made", PROPERTY_HINT_NONE, "Number of calls made"), "", "get_global_calls_made");
ADD_PROPERTY(PropertyInfo(Variant::INT, "monitor_global_exceptions", PROPERTY_HINT_NONE, "Number of exceptions thrown"), "", "get_global_exceptions");
ADD_PROPERTY(PropertyInfo(Variant::INT, "monitor_global_execution_timeouts", PROPERTY_HINT_NONE, "Number of execution timeouts"), "", "get_global_budget_overruns");
ADD_PROPERTY(PropertyInfo(Variant::INT, "monitor_global_execution_timeouts", PROPERTY_HINT_NONE, "Number of execution timeouts"), "", "get_global_timeouts");

ClassDB::bind_static_method("Sandbox", D_METHOD("get_global_instance_count"), &Sandbox::get_global_instance_count);
ClassDB::bind_static_method("Sandbox", D_METHOD("get_accumulated_startup_time"), &Sandbox::get_accumulated_startup_time);
Expand All @@ -85,6 +88,10 @@ void Sandbox::_bind_methods() {
Sandbox::Sandbox() {
this->m_use_unboxed_arguments = SandboxProjectSettings::use_native_types();
this->m_global_instance_count += 1;
// For each call state, reset the state
for (CurrentState &state : this->m_states) {
state.initialize(this->m_max_refs);
}
// In order to reduce checks we guarantee that this
// class is well-formed at all times.
try {
Expand Down Expand Up @@ -363,7 +370,7 @@ GuestVariant *Sandbox::setup_arguments(gaddr_t &sp, const Variant **args, int ar
Variant Sandbox::vmcall_internal(gaddr_t address, const Variant **args, int argc) {
CurrentState &state = this->m_states[m_level];
const bool is_reentrant_call = m_level > 1;
state.reset(this->m_level, this->m_max_refs);
state.reset(this->m_level);
m_level++;

// Scoped objects and owning tree node
Expand All @@ -385,7 +392,7 @@ Variant Sandbox::vmcall_internal(gaddr_t address, const Variant **args, int argc
// set up each argument, and return value
retvar = this->setup_arguments(sp, args, argc);
// execute!
m_machine->simulate_with(get_instructions_max() << 30, 0u, address);
m_machine->simulate_with(get_instructions_max() << 20, 0u, address);
} else if (m_level < MAX_LEVEL) {
riscv::Registers<RISCV_ARCH> regs;
regs = cpu.registers();
Expand All @@ -396,7 +403,7 @@ Variant Sandbox::vmcall_internal(gaddr_t address, const Variant **args, int argc
// set up each argument, and return value
retvar = this->setup_arguments(sp, args, argc);
// execute!
cpu.preempt_internal(regs, true, address, get_instructions_max() << 30);
cpu.preempt_internal(regs, true, address, get_instructions_max() << 20);
} else {
throw std::runtime_error("Recursion level exceeded");
}
Expand Down Expand Up @@ -515,20 +522,19 @@ int64_t Sandbox::get_heap_usage() const {
//-- Scoped objects and variants --//

unsigned Sandbox::add_scoped_variant(const Variant *value) const {
if (state().scoped_variants.size() >= this->m_max_refs) {
if (state().scoped_variants.size() >= state().variants.capacity()) {
ERR_PRINT("Maximum number of scoped variants reached.");
throw std::runtime_error("Maximum number of scoped variants reached.");
}
state().scoped_variants.push_back(value);
return state().scoped_variants.size() - 1 + 1; // 1-based index
}
unsigned Sandbox::create_scoped_variant(Variant &&value) const {
if (state().scoped_variants.size() >= this->m_max_refs) {
if (state().scoped_variants.size() >= state().variants.capacity()) {
ERR_PRINT("Maximum number of scoped variants reached.");
throw std::runtime_error("Maximum number of scoped variants reached.");
}
state().variants.emplace_back(std::move(value));
state().scoped_variants.push_back(&state().variants.back());
state().append(std::move(value));
return state().scoped_variants.size() - 1 + 1; // 1-based index
}
std::optional<const Variant *> Sandbox::get_scoped_variant(unsigned index) const noexcept {
Expand All @@ -551,11 +557,11 @@ Variant &Sandbox::get_mutable_scoped_variant(unsigned index) {
});
if (it == state().variants.end()) {
// Create a new variant in the list using the existing one, and return it
if (state().variants.size() >= this->m_max_refs) {
if (state().variants.size() >= state().variants.capacity()) {
ERR_PRINT("Maximum number of scoped variants reached.");
throw std::runtime_error("Maximum number of scoped variants reached.");
}
state().variants.emplace_back(*var);
state().append(Variant(*var));
return state().variants.back();
}
return *it;
Expand Down Expand Up @@ -681,8 +687,11 @@ bool Sandbox::get_property(const StringName &name, Variant &r_ret) {
} else if (name == StringName("monitor_heap_usage")) {
r_ret = get_heap_usage();
return true;
} else if (name == StringName("monitor_exceptions")) {
r_ret = get_exceptions();
return true;
} else if (name == StringName("monitor_execution_timeouts")) {
r_ret = get_budget_overruns();
r_ret = get_timeouts();
return true;
} else if (name == StringName("monitor_calls_made")) {
r_ret = get_calls_made();
Expand All @@ -693,8 +702,8 @@ bool Sandbox::get_property(const StringName &name, Variant &r_ret) {
} else if (name == StringName("global_exceptions")) {
r_ret = get_global_exceptions();
return true;
} else if (name == StringName("global_budget_overruns")) {
r_ret = get_global_budget_overruns();
} else if (name == StringName("global_timeouts")) {
r_ret = get_global_timeouts();
return true;
} else if (name == StringName("monitor_accumulated_startup_time")) {
r_ret = get_accumulated_startup_time();
Expand Down Expand Up @@ -736,3 +745,19 @@ Variant SandboxProperty::get(const Sandbox &sandbox) const {
}
return const_cast<Sandbox &>(sandbox).vmcall_internal(m_getter_address, nullptr, 0);
}

void Sandbox::CurrentState::initialize(unsigned max_refs) {
this->variants.reserve(max_refs);
}

void Sandbox::set_max_refs(uint32_t max) {
this->m_max_refs = max;
// If we are not in a call, reset the states
if (this->m_level == 1) {
for (CurrentState &state : this->m_states) {
state.initialize(max);
}
} else {
ERR_PRINT("Sandbox: Cannot change max references during a Sandbox call.");
}
}
34 changes: 21 additions & 13 deletions src/sandbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include <godot_cpp/classes/control.hpp>

#include <deque>
#include <godot_cpp/core/binder_common.hpp>
#include <libriscv/machine.hpp>
#include <optional>
Expand Down Expand Up @@ -38,10 +37,11 @@ class Sandbox : public Node {
String _to_string() const;

public:
static constexpr unsigned MAX_INSTRUCTIONS = 16; // Billions
static constexpr unsigned MAX_INSTRUCTIONS = 8000; // Millions
static constexpr unsigned MAX_HEAP = 16ul; // MBs
static constexpr unsigned MAX_VMEM = 16ul; // MBs
static constexpr unsigned MAX_LEVEL = 8; // Maximum call recursion depth
static constexpr unsigned MAX_LEVEL = 4; // Maximum call recursion depth
static constexpr unsigned MAX_REFS = 100; // Default maximum number of references
static constexpr unsigned EDITOR_THROTTLE = 8; // Throttle VM calls from the editor
static constexpr unsigned MAX_PROPERTIES = 16; // Maximum number of sandboxed properties

Expand All @@ -50,7 +50,9 @@ class Sandbox : public Node {
std::vector<const Variant *> scoped_variants;
std::vector<uintptr_t> scoped_objects;

inline void reset(unsigned index, unsigned max_refs);
void append(Variant &&value);
void initialize(unsigned max_refs);
void reset(unsigned index);
};

Sandbox();
Expand Down Expand Up @@ -104,19 +106,21 @@ class Sandbox : public Node {
// -= Sandbox Properties =-

uint32_t get_max_refs() const { return m_max_refs; }
void set_max_refs(uint32_t max) { m_max_refs = max; }
void set_max_refs(uint32_t max);
void set_memory_max(uint32_t max) { m_memory_max = max; }
uint32_t get_memory_max() const { return m_memory_max; }
void set_instructions_max(int64_t max) { m_insn_max = max; }
int64_t get_instructions_max() const { return m_insn_max; }
void set_heap_usage(int64_t) {} // Do nothing (it's a read-only property)
int64_t get_heap_usage() const;
void set_budget_overruns(unsigned budget) {} // Do nothing (it's a read-only property)
unsigned get_budget_overruns() const { return m_budget_overruns; }
void set_exceptions(unsigned exceptions) {} // Do nothing (it's a read-only property)
unsigned get_exceptions() const { return m_exceptions; }
void set_timeouts(unsigned budget) {} // Do nothing (it's a read-only property)
unsigned get_timeouts() const { return m_timeouts; }
void set_calls_made(unsigned calls) {} // Do nothing (it's a read-only property)
unsigned get_calls_made() const { return m_calls_made; }

static uint64_t get_global_budget_overruns() { return m_global_budget_overruns; }
static uint64_t get_global_timeouts() { return m_global_timeouts; }
static uint64_t get_global_exceptions() { return m_global_exceptions; }
static uint64_t get_global_calls_made() { return m_global_calls_made; }

Expand Down Expand Up @@ -267,7 +271,7 @@ class Sandbox : public Node {
machine_t *m_machine = nullptr;
godot::Node *m_tree_base = nullptr;
const PackedByteArray *m_binary = nullptr;
uint32_t m_max_refs = 100;
uint32_t m_max_refs = MAX_REFS;
uint32_t m_memory_max = MAX_VMEM;
int64_t m_insn_max = MAX_INSTRUCTIONS;

Expand All @@ -279,7 +283,7 @@ class Sandbox : public Node {
bool m_use_unboxed_arguments = false;

// Stats
unsigned m_budget_overruns = 0;
unsigned m_timeouts = 0;
unsigned m_exceptions = 0;
unsigned m_calls_made = 0;

Expand All @@ -293,15 +297,19 @@ class Sandbox : public Node {
mutable std::vector<SandboxProperty> m_properties;

// Global statistics
static inline uint64_t m_global_budget_overruns = 0;
static inline uint64_t m_global_timeouts = 0;
static inline uint64_t m_global_exceptions = 0;
static inline uint64_t m_global_calls_made = 0;
static inline uint32_t m_global_instance_count = 0;
static inline double m_accumulated_startup_time = 0.0;
};

inline void Sandbox::CurrentState::reset(unsigned index, unsigned max_refs) {
variants.reserve(max_refs);
inline void Sandbox::CurrentState::append(Variant &&value) {
variants.push_back(std::move(value));
scoped_variants.push_back(&variants.back());
}

inline void Sandbox::CurrentState::reset(unsigned index) {
variants.clear();
scoped_variants.clear();
scoped_objects.clear();
Expand Down
6 changes: 3 additions & 3 deletions src/sandbox_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ void Sandbox::handle_exception(gaddr_t address) {
}

void Sandbox::handle_timeout(gaddr_t address) {
this->m_budget_overruns++;
Sandbox::m_global_budget_overruns++;
this->m_timeouts++;
Sandbox::m_global_timeouts++;
auto callsite = machine().memory.lookup(address);
UtilityFunctions::print(
"Sandbox: Timeout for '", callsite.name.c_str(),
"' (Timeouts: ", m_budget_overruns, "\n");
"' (Timeouts: ", m_timeouts, ")\n");
}

void Sandbox::print_backtrace(const gaddr_t addr) {
Expand Down
Loading

0 comments on commit 9a9474c

Please sign in to comment.