mirror of
https://github.com/Vita3K/Vita3K.git
synced 2026-07-11 01:34:23 +02:00
Implement vargs and printf functions (#322)
* module: Implement vargs and printf functions - Source code of printf is here: https://github.com/mpaland/printf, modify for vita3k usage * [module] Pass layout state as argument rather than tuple * module: Get rid of state array and don't continue layout if meet varargs * module/test: Readjust test as requests and add test for vargs * module: Remove unneccesary thin function is_arg_vargs * module: Add newline
This commit is contained in:
+4
-1
@@ -55,6 +55,9 @@
|
||||
[submodule "src/external/dlmalloc"]
|
||||
path = src/external/dlmalloc
|
||||
url = https://github.com/Vita3K/dlmalloc
|
||||
[submodule "src/external/printf"]
|
||||
path = src/external/printf
|
||||
url = https://github.com/vita3k/printf
|
||||
[submodule "src/external/imgui_club"]
|
||||
path = src/external/imgui_club
|
||||
url = https://github.com/ocornut/imgui_club.git
|
||||
url = https://github.com/ocornut/imgui_club.git
|
||||
@@ -9,11 +9,12 @@ add_library(
|
||||
include/module/read_arg.h
|
||||
include/module/write_return_value.h
|
||||
src/module.cpp
|
||||
src/read_arg.cpp
|
||||
src/write_return_value.cpp
|
||||
)
|
||||
|
||||
target_include_directories(module PUBLIC include)
|
||||
target_link_libraries(module PUBLIC kernel host microprofile rpcs3 dlmalloc)
|
||||
target_link_libraries(module PUBLIC kernel host microprofile rpcs3 dlmalloc printf)
|
||||
|
||||
add_executable(
|
||||
module-tests
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
|
||||
enum class ArgLocation {
|
||||
gpr,
|
||||
@@ -30,5 +31,11 @@ struct ArgLayout {
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
struct LayoutArgsState {
|
||||
size_t gpr_used;
|
||||
size_t stack_used;
|
||||
size_t float_used;
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
using ArgsLayout = std::array<ArgLayout, sizeof...(Args)>;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "lay_out_args.h"
|
||||
#include "read_arg.h"
|
||||
#include "write_return_value.h"
|
||||
#include "vargs.h"
|
||||
|
||||
#include <host/imports.h>
|
||||
#include <host/state.h>
|
||||
@@ -28,25 +29,25 @@
|
||||
|
||||
// Function returns a value that is written to CPU registers.
|
||||
template <typename Ret, typename... Args, size_t... indices>
|
||||
void call(Ret (*export_fn)(HostState &, SceUID, const char *, Args...), const char *export_name, const ArgsLayout<Args...> &args_layout, std::index_sequence<indices...>, SceUID thread_id, CPUState &cpu, HostState &host) {
|
||||
const Ret ret = (*export_fn)(host, thread_id, export_name, read<Args, indices, Args...>(cpu, args_layout, host.mem)...);
|
||||
void call(Ret (*export_fn)(HostState &, SceUID, const char *, Args...), const char *export_name, const ArgsLayout<Args...> &args_layout, const LayoutArgsState &state, std::index_sequence<indices...>, SceUID thread_id, CPUState &cpu, HostState &host) {
|
||||
const Ret ret = (*export_fn)(host, thread_id, export_name, read<Args, indices, Args...>(cpu, args_layout, state, host.mem)...);
|
||||
write_return_value(cpu, ret);
|
||||
}
|
||||
|
||||
// Function does not return a value.
|
||||
template <typename... Args, size_t... indices>
|
||||
void call(void (*export_fn)(HostState &, SceUID, const char *, Args...), const char *export_name, const ArgsLayout<Args...> &args_layout, std::index_sequence<indices...>, SceUID thread_id, CPUState &cpu, HostState &host) {
|
||||
(*export_fn)(host, thread_id, export_name, read<Args, indices, Args...>(cpu, args_layout, host.mem)...);
|
||||
void call(void (*export_fn)(HostState &, SceUID, const char *, Args...), const char *export_name, const ArgsLayout<Args...> &args_layout, const LayoutArgsState &state, std::index_sequence<indices...>, SceUID thread_id, CPUState &cpu, HostState &host) {
|
||||
(*export_fn)(host, thread_id, export_name, read<Args, indices, Args...>(cpu, args_layout, state, host.mem)...);
|
||||
}
|
||||
|
||||
template <typename Ret, typename... Args>
|
||||
ImportFn bridge(Ret (*export_fn)(HostState &, SceUID, const char *, Args...), const char *export_name) {
|
||||
constexpr ArgsLayout<Args...> args_layout = lay_out<typename BridgeTypes<Args>::ArmType...>();
|
||||
constexpr std::tuple<ArgsLayout<Args...>, LayoutArgsState> args_layout = lay_out<typename BridgeTypes<Args>::ArmType...>();
|
||||
|
||||
return [export_fn, export_name, args_layout](HostState &host, CPUState &cpu, SceUID thread_id) {
|
||||
MICROPROFILE_SCOPEI("HLE", export_name, MP_YELLOW);
|
||||
|
||||
using Indices = std::index_sequence_for<Args...>;
|
||||
call(export_fn, export_name, args_layout, Indices(), thread_id, cpu, host);
|
||||
call(export_fn, export_name, std::get<0>(args_layout), std::get<1>(args_layout), Indices(), thread_id, cpu, host);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,11 +23,9 @@
|
||||
|
||||
#include <tuple>
|
||||
|
||||
struct LayoutArgsState {
|
||||
size_t gpr_used;
|
||||
size_t stack_used;
|
||||
size_t float_used;
|
||||
};
|
||||
namespace module {
|
||||
class vargs;
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
constexpr std::tuple<ArgLayout, LayoutArgsState> add_to_stack(const LayoutArgsState &state) {
|
||||
@@ -91,24 +89,31 @@ constexpr std::enable_if_t<sizeof...(Args) == 0> add_args_to_layout(ArgLayout &h
|
||||
// One or more arguments to add.
|
||||
template <typename Head, typename... Tail>
|
||||
constexpr void add_args_to_layout(ArgLayout &head, LayoutArgsState &state) {
|
||||
// Add the argument at the head of the list.
|
||||
const std::tuple<ArgLayout, LayoutArgsState> result = add_arg_to_layout<Head>(state);
|
||||
head = std::get<0>(result);
|
||||
state = std::get<1>(result);
|
||||
// Returns immidiately if the Head is an varargs
|
||||
if constexpr(std::is_same_v<Head, module::vargs>) {
|
||||
return;
|
||||
} else {
|
||||
// Add the argument at the head of the list.
|
||||
const std::tuple<ArgLayout, LayoutArgsState> result = add_arg_to_layout<Head>(state);
|
||||
head = std::get<0>(result);
|
||||
state = std::get<1>(result);
|
||||
|
||||
// Recursively add the remaining arguments.
|
||||
if (sizeof...(Tail) > 0) {
|
||||
add_args_to_layout<Tail...>(*(&head + 1), state);
|
||||
// Recursively add the remaining arguments.
|
||||
// If the last argument is varargs, abadon adding
|
||||
if (sizeof...(Tail) > 0) {
|
||||
add_args_to_layout<Tail...>(*(&head + 1), state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
constexpr ArgsLayout<Args...> lay_out() {
|
||||
constexpr std::tuple<ArgsLayout<Args...>, LayoutArgsState> lay_out() {
|
||||
LayoutArgsState state {};
|
||||
ArgsLayout<Args...> layout = {};
|
||||
|
||||
if (sizeof...(Args) > 0) {
|
||||
LayoutArgsState state = {};
|
||||
add_args_to_layout<Args...>(*layout.data(), state);
|
||||
add_args_to_layout<Args...>(layout[0], state);
|
||||
}
|
||||
|
||||
return layout;
|
||||
return { layout, state };
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
|
||||
#include <cpu/functions.h>
|
||||
|
||||
namespace module {
|
||||
class vargs;
|
||||
}
|
||||
|
||||
// Read 32-bit (or smaller) values from a single register.
|
||||
template <typename T>
|
||||
std::enable_if_t<sizeof(T) <= 4, T> read_from_gpr(CPUState &cpu, const ArgLayout &arg) {
|
||||
@@ -64,10 +68,19 @@ T read(CPUState &cpu, const ArgLayout &arg, const MemState &mem) {
|
||||
return T();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T make_vargs(const LayoutArgsState &state);
|
||||
|
||||
template <typename Arg, size_t index, typename... Args>
|
||||
Arg read(CPUState &cpu, const ArgsLayout<Args...> &args, const MemState &mem) {
|
||||
Arg read(CPUState &cpu, const ArgsLayout<Args...> &args, const LayoutArgsState &state, const MemState &mem) {
|
||||
using ArmType = typename BridgeTypes<Arg>::ArmType;
|
||||
|
||||
const ArmType bridged = read<ArmType>(cpu, args[index], mem);
|
||||
return BridgeTypes<Arg>::arm_to_host(bridged, mem);
|
||||
// Note (bentokun): The else block was intentionally made to workaround evaluation
|
||||
// fault where MSVC evaluates the rest of the function when the Arg type is vargs
|
||||
if constexpr(std::is_same_v<Arg, module::vargs>) {
|
||||
return make_vargs<Arg>(state);
|
||||
} else {
|
||||
const ArmType bridged = read<ArmType>(cpu, args[index], mem);
|
||||
return BridgeTypes<Arg>::arm_to_host(bridged, mem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2018 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "args_layout.h"
|
||||
#include "lay_out_args.h"
|
||||
#include "read_arg.h"
|
||||
|
||||
namespace module {
|
||||
class vargs {
|
||||
LayoutArgsState layoutState;
|
||||
ArgLayout currentLayout;
|
||||
|
||||
public:
|
||||
vargs() {}
|
||||
|
||||
explicit vargs(LayoutArgsState layoutState)
|
||||
: layoutState(layoutState) {
|
||||
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T next(CPUState &cpu, MemState &mem) {
|
||||
const auto state_tuple = add_arg_to_layout<T>(layoutState);
|
||||
|
||||
layoutState = std::move(std::get<1>(state_tuple));
|
||||
currentLayout = std::move(std::get<0>(state_tuple));
|
||||
|
||||
return read<T>(cpu, currentLayout, mem);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include <module/read_arg.h>
|
||||
#include <module/vargs.h>
|
||||
|
||||
// Workaround for old Clang and GCC
|
||||
template <>
|
||||
module::vargs make_vargs(const LayoutArgsState &state) {
|
||||
return module::vargs {state};
|
||||
}
|
||||
@@ -23,6 +23,10 @@ static bool operator==(const ArgLayout &a, const ArgLayout &b) {
|
||||
return (a.location == b.location) && (a.offset == b.offset);
|
||||
}
|
||||
|
||||
static bool operator==(const LayoutArgsState &a, const LayoutArgsState &b) {
|
||||
return (a.float_used == b.float_used) && (a.gpr_used == b.gpr_used) && (a.stack_used == b.stack_used);
|
||||
}
|
||||
|
||||
static std::ostream &operator<<(std::ostream &out, const ArgLayout &layout) {
|
||||
switch (layout.location) {
|
||||
case ArgLocation::gpr:
|
||||
@@ -41,7 +45,7 @@ static std::ostream &operator<<(std::ostream &out, const ArgLayout &layout) {
|
||||
|
||||
TEST(lay_out, gpr_overflows_to_stack) {
|
||||
const auto actual = lay_out<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t>();
|
||||
const std::array<ArgLayout, 6> expected = { {
|
||||
const std::array<ArgLayout, 6> layouts = { {
|
||||
{ ArgLocation::gpr, 0 },
|
||||
{ ArgLocation::gpr, 1 },
|
||||
{ ArgLocation::gpr, 2 },
|
||||
@@ -50,38 +54,74 @@ TEST(lay_out, gpr_overflows_to_stack) {
|
||||
{ ArgLocation::stack, 4 },
|
||||
} };
|
||||
|
||||
ASSERT_EQ(actual, expected);
|
||||
// Using full 4 gprs. Stack used for int32_t and uint32_t, which is total of 8 bytes
|
||||
const LayoutArgsState state = {
|
||||
4, 8, 0
|
||||
};
|
||||
|
||||
ASSERT_EQ(std::get<0>(actual), layouts);
|
||||
ASSERT_EQ(std::get<1>(actual), state);
|
||||
}
|
||||
|
||||
TEST(lay_out, dword_uses_two_gpr) {
|
||||
const auto actual = lay_out<int64_t, int32_t>();
|
||||
const std::array<ArgLayout, 2> expected = { {
|
||||
const std::array<ArgLayout, 2> layouts = { {
|
||||
{ ArgLocation::gpr, 0 },
|
||||
{ ArgLocation::gpr, 2 },
|
||||
} };
|
||||
|
||||
ASSERT_EQ(actual, expected);
|
||||
const LayoutArgsState state = {
|
||||
3, 0, 0
|
||||
};
|
||||
|
||||
ASSERT_EQ(std::get<0>(actual), layouts);
|
||||
ASSERT_EQ(std::get<1>(actual), state);
|
||||
}
|
||||
|
||||
TEST(lay_out, dword_aligned_to_two_gpr) {
|
||||
const auto actual = lay_out<int32_t, int64_t>();
|
||||
const std::array<ArgLayout, 2> expected = { {
|
||||
const std::array<ArgLayout, 2> layouts = { {
|
||||
{ ArgLocation::gpr, 0 },
|
||||
{ ArgLocation::gpr, 2 },
|
||||
} };
|
||||
|
||||
ASSERT_EQ(actual, expected);
|
||||
const LayoutArgsState state = {
|
||||
4, 0, 0
|
||||
};
|
||||
|
||||
ASSERT_EQ(std::get<0>(actual), layouts);
|
||||
ASSERT_EQ(std::get<1>(actual), state);
|
||||
}
|
||||
|
||||
TEST(lay_out, dword_alignment_can_waste_gprs) {
|
||||
// Arg 3 could fit in r1, but alignment of arg 2 skipped it.
|
||||
// I've not observed this, but this is the current behaviour.
|
||||
const auto actual = lay_out<int32_t, int64_t, int32_t>();
|
||||
const std::array<ArgLayout, 3> expected = { {
|
||||
const std::array<ArgLayout, 3> layouts = { {
|
||||
{ ArgLocation::gpr, 0 },
|
||||
{ ArgLocation::gpr, 2 },
|
||||
{ ArgLocation::stack, 0 },
|
||||
} };
|
||||
|
||||
ASSERT_EQ(actual, expected);
|
||||
const LayoutArgsState state = {
|
||||
4, 4, 0
|
||||
};
|
||||
|
||||
ASSERT_EQ(std::get<0>(actual), layouts);
|
||||
ASSERT_EQ(std::get<1>(actual), state);
|
||||
}
|
||||
|
||||
TEST(lay_out, vargs_does_not_affect_layout) {
|
||||
const auto actual = lay_out<int16_t, int16_t, module::vargs>();
|
||||
const std::array<ArgLayout, 3> layouts = { {
|
||||
{ ArgLocation::gpr, 0 },
|
||||
{ ArgLocation::gpr, 1 }
|
||||
} };
|
||||
|
||||
const LayoutArgsState state = {
|
||||
2, 0, 0
|
||||
};
|
||||
|
||||
ASSERT_EQ(std::get<0>(actual), layouts);
|
||||
ASSERT_EQ(std::get<1>(actual), state);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include "SceLibKernel.h"
|
||||
#include <v3kprintf.h>
|
||||
|
||||
#include <cpu/functions.h>
|
||||
#include <dlmalloc.h>
|
||||
@@ -176,15 +177,41 @@ EXPORT(int, sceClibMspaceReallocalign) {
|
||||
return UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
EXPORT(int, sceClibPrintf, const char *format, void *args) {
|
||||
// TODO args
|
||||
LOG_INFO("{}", format);
|
||||
EXPORT(int, sceClibPrintf, const char *format, module::vargs args) {
|
||||
std::string buffer;
|
||||
buffer.resize(1024);
|
||||
|
||||
const ThreadStatePtr thread = lock_and_find(thread_id, host.kernel.threads, host.kernel.mutex);
|
||||
|
||||
if (!thread) {
|
||||
return SCE_KERNEL_ERROR_UNKNOWN_THREAD_ID;
|
||||
}
|
||||
|
||||
int result = utils::sprintf(&buffer[0], format, *(thread->cpu), host.mem, args);
|
||||
|
||||
if (!result) {
|
||||
return SCE_KERNEL_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
LOG_INFO("{}", buffer);
|
||||
|
||||
return SCE_KERNEL_OK;
|
||||
}
|
||||
|
||||
EXPORT(int, sceClibSnprintf, char *dest, SceSize size, const char *format, void *args) {
|
||||
// TODO args
|
||||
return snprintf(dest, size, "%s", format);
|
||||
EXPORT(int, sceClibSnprintf, char *dest, SceSize size, const char *format, module::vargs args) {
|
||||
const ThreadStatePtr thread = lock_and_find(thread_id, host.kernel.threads, host.kernel.mutex);
|
||||
|
||||
if (!thread) {
|
||||
return SCE_KERNEL_ERROR_UNKNOWN_THREAD_ID;
|
||||
}
|
||||
|
||||
int result = utils::snprintf(dest, size, format, *(thread->cpu), host.mem, args);
|
||||
|
||||
if (!result) {
|
||||
return SCE_KERNEL_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
return SCE_KERNEL_OK;
|
||||
}
|
||||
|
||||
EXPORT(int, sceClibSnprintfChk) {
|
||||
|
||||
Vendored
+3
@@ -45,6 +45,9 @@ if(WIN32)
|
||||
target_include_directories(dirent INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/dirent/include")
|
||||
endif()
|
||||
|
||||
add_library(printf INTERFACE)
|
||||
target_include_directories(printf INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/printf")
|
||||
|
||||
add_library(elfio INTERFACE)
|
||||
target_include_directories(elfio INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/elfio")
|
||||
|
||||
|
||||
+1
Submodule src/external/printf added at b0585c7f8d
Reference in New Issue
Block a user