Skip to content

Commit

Permalink
Thunks/gen: Implement host_layout->guest_layout conversion
Browse files Browse the repository at this point in the history
This enables use of guest_layout for return values.
  • Loading branch information
neobrain committed Dec 22, 2023
1 parent 6904911 commit 1e0620f
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 4 deletions.
49 changes: 46 additions & 3 deletions ThunkLibs/Generator/gen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ void GenerateThunkLibsAction::EmitLayoutWrappers(
// Disallow use of layout wrappers for this type by specializing without a definition
fmt::print(file, "template<>\nstruct guest_layout<{}>;\n", struct_name);
fmt::print(file, "template<>\nstruct host_layout<{}>;\n", struct_name);
fmt::print(file, "guest_layout<{}> to_guest(const host_layout<{}>&) = delete;\n", struct_name, struct_name);
continue;
}

Expand Down Expand Up @@ -234,6 +235,41 @@ void GenerateThunkLibsAction::EmitLayoutWrappers(
}
fmt::print(file, " }}\n");
fmt::print(file, "}};\n\n");

// Guest->host layout conversion
fmt::print(file, "inline guest_layout<{}> to_guest(const host_layout<{}>& from) {{\n", struct_name, struct_name);
if (type_compat.at(type) == TypeCompatibility::Full) {
fmt::print(file, " guest_layout<{}> ret;\n", struct_name);
fmt::print(file, " static_assert(sizeof(from) == sizeof(ret));\n");
fmt::print(file, " memcpy(&ret, &from, sizeof(from));\n");
} else {
// Conversion needs struct repacking.
// Wrapping each member in `to_guest(to_host_layout(...))` ensures this is done recursively.
fmt::print(file, " guest_layout<{}> ret {{ .data {{\n", struct_name);
auto map_field2 = [&file](const StructInfo::MemberInfo& member, bool skip_arrays) {
auto& decl_name = member.member_name;
auto& array_size = member.array_size;
if (!array_size && skip_arrays) {
fmt::print(file, " .{} = to_guest(to_host_layout(from.data.{})),\n", decl_name, decl_name);
} else if (array_size && !skip_arrays) {
// Copy element-wise below
fmt::print(file, " for (size_t i = 0; i < {}; ++i) {{\n", array_size.value());
fmt::print(file, " ret.data.{}.data[i] = to_guest(to_host_layout(from.data.{}[i]));\n", decl_name, decl_name);
fmt::print(file, " }}\n");
}
};

// Prefer initialization via the constructor's initializer list if possible (to detect unintended narrowing), otherwise initialize in the body
for (auto& member : guest_abi.at(struct_name).get_if_struct()->members) {
map_field2(member, true);
}
fmt::print(file, " }} }};\n");
for (auto& member : guest_abi.at(struct_name).get_if_struct()->members) {
map_field2(member, false);
}
}
fmt::print(file, " return ret;\n");
fmt::print(file, "}}\n\n");
}
}

Expand Down Expand Up @@ -501,7 +537,7 @@ void GenerateThunkLibsAction::OnAnalysisComplete(clang::ASTContext& context) {
fmt::print(file, " guest_layout<{}> a_{};\n", get_type_name(context, thunk.param_types[idx].getTypePtr()), idx);
}
if (!thunk.return_type->isVoidType()) {
file << " " << format_decl(thunk.return_type, "rv") << ";\n";
fmt::print(file, " guest_layout<{}> rv;\n", get_type_name(context, thunk.return_type.getTypePtr()));
} else if (thunk.param_types.size() == 0) {
// Avoid "empty struct has size 0 in C, size 1 in C++" warning
file << " char force_nonempty;\n";
Expand Down Expand Up @@ -552,7 +588,10 @@ void GenerateThunkLibsAction::OnAnalysisComplete(clang::ASTContext& context) {
}
}

file << (thunk.return_type->isVoidType() ? " " : " args->rv = ") << function_to_call << "(";
if (!thunk.return_type->isVoidType()) {
fmt::print(file, " args->rv = to_guest(to_host_layout<{}>(", thunk.return_type.getAsString());
}
fmt::print(file, "{}(", function_to_call);
{
auto format_param = [&](std::size_t idx) {
std::string raw_arg = fmt::format("a_{}.data", idx);
Expand All @@ -574,7 +613,11 @@ void GenerateThunkLibsAction::OnAnalysisComplete(clang::ASTContext& context) {

file << format_function_args(thunk, format_param);
}
file << ");\n";
if (!thunk.return_type->isVoidType()) {
fmt::print(file, "))");
}
fmt::print(file, ");\n");

file << "}\n";
}
file << "}\n";
Expand Down
18 changes: 18 additions & 0 deletions ThunkLibs/include/common/Host.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,24 @@ struct host_layout {
}
};

// Explicitly turn a host type into its corresponding host_layout
template<typename T>
const host_layout<T>& to_host_layout(const T& t) {
static_assert(std::is_same_v<decltype(host_layout<T>::data), T>);
return reinterpret_cast<const host_layout<T>&>(t);
}

template<typename T>
inline guest_layout<T> to_guest(const host_layout<T>& from) {
if constexpr (std::is_enum_v<T>) {
// enums are represented by fixed-size integers in guest_layout, so explicitly cast them
return guest_layout<T> { static_cast<std::underlying_type_t<T>>(from.data) };
} else {
guest_layout<T> ret { .data = from.data };
return ret;
}
}

template<typename>
struct CallbackUnpack;

Expand Down
19 changes: 18 additions & 1 deletion unittests/ThunkLibs/generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,9 @@ SourceWithAST Fixture::run_thunkgen_host(std::string_view prelude, std::string_v
" host_layout(const guest_layout<T>& from);\n"
"};\n"
"\n"
"template<typename F> void FinalizeHostTrampolineForGuestFunction(F*);\n";
"template<typename T> guest_layout<T> to_guest(const host_layout<T>& from);\n"
"template<typename F> void FinalizeHostTrampolineForGuestFunction(F*);\n"
"template<typename T> const host_layout<T>& to_host_layout(const T& t);\n";

auto& filename = output_filenames.host;
{
Expand Down Expand Up @@ -554,6 +556,17 @@ TEST_CASE_METHOD(Fixture, "LayoutWrappers") {
return !decl->isCompleteDefinition();
});
};
const auto guest_converter_defined =
matches(functionDecl(hasName("to_guest"),
// Parameter is a host_layout<A> (ignoring qualifiers and references)
hasParameter(0, hasType(references(classTemplateSpecializationDecl(hasName("host_layout"), hasAnyTemplateArgument(refersToType(asString("struct A"))))))),
// Return value is a guest_layout<A>
returns(asString("guest_layout<" CLANG_STRUCT_PREFIX "A>"))));
const auto guest_converter_undefined =
matches(functionDecl(hasName("to_guest"),
// Parameter is a host_layout<A> (ignoring qualifiers and references)
hasParameter(0, hasType(references(classTemplateSpecializationDecl(hasName("host_layout"), hasAnyTemplateArgument(refersToType(asString("struct A"))))))),
isDeleted()));

const std::string code =
"template<typename> struct fex_gen_type {};\n"
Expand All @@ -570,6 +583,7 @@ TEST_CASE_METHOD(Fixture, "LayoutWrappers") {
hasAnyTemplateArgument(refersToType(asString("struct A"))),
has(fieldDecl(hasName("data"), hasType(hasCanonicalType(asString("struct A")))))
)));
CHECK_THAT(output, guest_converter_defined);

CHECK_THAT(output, host_layout_is_trivial);
}
Expand All @@ -594,6 +608,7 @@ TEST_CASE_METHOD(Fixture, "LayoutWrappers") {
has(fieldDecl(hasName("b"), hasType(asString("guest_layout<int>"))))
))))))
)));
CHECK_THAT(output, guest_converter_defined);

CHECK_THAT(output, host_layout_is_trivial);
}
Expand All @@ -608,6 +623,7 @@ TEST_CASE_METHOD(Fixture, "LayoutWrappers") {
"#endif\n";
const auto output = run_thunkgen_host(struct_def, code, guest_abi);
CHECK_THAT(output, layout_undefined("guest_layout"));
CHECK_THAT(output, guest_converter_undefined);
CHECK_THAT(output, layout_undefined("host_layout"));
}

Expand Down Expand Up @@ -639,6 +655,7 @@ TEST_CASE_METHOD(Fixture, "LayoutWrappers") {
has(fieldDecl(hasName("b"), hasType(asString("guest_layout<int>"))))
))))))
)));
CHECK_THAT(output, guest_converter_defined);

CHECK_THAT(output, host_layout_is_trivial);
}
Expand Down

0 comments on commit 1e0620f

Please sign in to comment.