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:
pent0
2019-03-13 14:44:56 +07:00
committed by GitHub
parent 4279f9678b
commit 06d0474021
12 changed files with 199 additions and 41 deletions
+4 -1
View File
@@ -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
+2 -1
View File
@@ -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)>;
+7 -6
View File
@@ -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 };
}
+16 -3
View File
@@ -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);
}
};
}
+8
View File
@@ -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};
}
+48 -8
View File
@@ -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) {
+3
View File
@@ -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")
Vendored Submodule
+1
Submodule src/external/printf added at b0585c7f8d