Skip to content
Draft
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
82 changes: 79 additions & 3 deletions src/sgl/refl/function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,14 @@ const std::vector<ref<Function>>& Function::overloads() const
overloads.reserve(reflected_overloads.size());
for (uint32_t i = 0; i < reflected_overloads.size(); ++i) {
ref<const FunctionReflection> reflection = reflected_overloads[i];
if (reflection)
overloads.push_back(make_ref<Function>(m_layout, std::move(reflection), m_this_type, m_full_name));
if (reflection) {
ref<Function> overload = make_ref<Function>(m_layout, std::move(reflection), m_this_type, m_full_name);
std::vector<std::string> type_names;
for (const ref<const VariableReflection>& parameter : overload->reflection()->parameters())
type_names.push_back(parameter->type()->full_name());
add_derived_function(overload, DerivationKind::overload, std::move(type_names));
overloads.push_back(overload);
}
}
m_cached_overloads = std::move(overloads);
}
Expand All @@ -238,10 +244,13 @@ bool Function::is_constructor() const
ref<Function> Function::specialize_with_arg_types(const std::vector<ref<Type>>& types) const
{
std::vector<ref<TypeReflection>> reflections;
std::vector<std::string> type_names;
reflections.reserve(types.size());
type_names.reserve(types.size());
for (const ref<Type>& type : types) {
SGL_CHECK(type, "Cannot specialize function '{}' with null argument type", m_full_name);
reflections.emplace_back(ref(const_cast<TypeReflection*>(type->reflection())));
type_names.push_back(type->full_name());
}

ref<const FunctionReflection> reflection = m_reflection->specialize_with_arg_types(reflections);
Expand All @@ -251,7 +260,73 @@ ref<Function> Function::specialize_with_arg_types(const std::vector<ref<Type>>&
// Do not cache this result by name. The input types are concrete call argument
// types, not the canonical generic argument list, so deriving a full name here
// can pollute Layout's named-function cache with non-canonical spellings.
return make_ref<Function>(m_layout, std::move(reflection), m_this_type);
ref<Function> function = make_ref<Function>(m_layout, std::move(reflection), m_this_type);
add_derived_function(function, DerivationKind::specialization, std::move(type_names));
return function;
}

void Function::add_derived_function(
ref<Function> function,
DerivationKind kind,
std::vector<std::string> type_names
) const
{
m_derived_functions.push_back(
DerivedFunction{
.function = std::move(function),
.kind = kind,
.type_names = std::move(type_names),
}
);
}

void Function::refresh_derived_functions()
{
sgl::ProgramLayout* low_level_layout = const_cast<sgl::ProgramLayout*>(m_layout->low_level_layout());
for (const DerivedFunction& derived : m_derived_functions) {
ref<const FunctionReflection> reflection;
switch (derived.kind) {
case DerivationKind::overload: {
auto overloads = m_reflection->overloads();
reflection = nullptr;
for (const ref<const FunctionReflection>& overload : overloads) {
auto parameters = overload->parameters();
if (parameters.size() != derived.type_names.size())
continue;

bool matches = true;
for (uint32_t i = 0; i < parameters.size(); ++i) {
if (parameters[i]->type()->full_name() != derived.type_names[i]) {
matches = false;
break;
}
}
if (matches) {
reflection = overload;
break;
}
}
break;
}
case DerivationKind::specialization: {
std::vector<ref<TypeReflection>> types;
types.reserve(derived.type_names.size());
for (const std::string& type_name : derived.type_names) {
ref<const TypeReflection> type = low_level_layout->find_type_by_name(type_name.c_str());
if (!type) {
types.clear();
break;
}
types.emplace_back(ref(const_cast<TypeReflection*>(type.get())));
}
if (types.size() == derived.type_names.size())
reflection = m_reflection->specialize_with_arg_types(types);
break;
}
}
if (reflection)
derived.function->on_hot_reload(std::move(reflection));
}
}

void Function::on_hot_reload(ref<const FunctionReflection> reflection)
Expand All @@ -261,6 +336,7 @@ void Function::on_hot_reload(ref<const FunctionReflection> reflection)
m_cached_return_type.reset();
m_cached_parameters.reset();
m_cached_overloads.reset();
refresh_derived_functions();
}

std::string Function::to_string() const
Expand Down
13 changes: 13 additions & 0 deletions src/sgl/refl/function.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,23 @@ class SGL_API Function final : public Object {
std::string to_string() const override;

private:
enum class DerivationKind { overload, specialization };

struct DerivedFunction {
ref<Function> function;
DerivationKind kind;
std::vector<std::string> type_names;
};

void add_derived_function(ref<Function> function, DerivationKind kind, std::vector<std::string> type_names) const;
void refresh_derived_functions();

ref<Layout> m_layout;
ref<const FunctionReflection> m_reflection;
ref<Type> m_this_type;
std::string m_full_name;
// Direct children are retained so hot reload can refresh the derivation tree recursively.
mutable std::vector<DerivedFunction> m_derived_functions;

mutable std::optional<ref<Type>> m_cached_return_type;
mutable std::optional<std::vector<ref<Parameter>>> m_cached_parameters;
Expand Down
25 changes: 25 additions & 0 deletions tests/sgl/refl/test_reflection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@ struct Foo {
float value;
};
float get_value(Foo foo) { return foo.value; }
float overloaded(float value) { return value; }
int overloaded(int value) { return value; }
float generic_value<T>(T value) { return 0.0; }
)"
);
REQUIRE(module_a);
Expand All @@ -267,13 +270,32 @@ struct Foo {
int value;
};
int get_value(Foo foo) { return foo.value; }
int overloaded(int value) { return value; }
int overloaded(float value) { return int(value); }
int generic_value<T>(T value) { return 0; }
)"
);
REQUIRE(module_b);

ref<refl::Layout> layout = make_ref<refl::Layout>(module_a->layout());
ref<refl::Type> old_foo_type = layout->require_type_by_name("Foo");
ref<refl::Function> old_function = layout->require_function_by_name("get_value");
ref<refl::Type> float_type = layout->scalar_type(TypeReflection::ScalarType::float32);
ref<refl::Function> overloaded = layout->require_function_by_name("overloaded");
ref<refl::Function> old_overload;
for (const ref<refl::Function>& overload : overloaded->overloads()) {
if (overload->parameters().at(0)->type() == float_type)
old_overload = overload;
}
REQUIRE(old_overload);
CHECK(old_overload->return_type() == float_type);
ref<refl::Function> old_nested_specialization = old_overload->specialize_with_arg_types({float_type});
REQUIRE(old_nested_specialization);
CHECK(old_nested_specialization->return_type() == float_type);
ref<refl::Function> generic = layout->require_function_by_name("generic_value");
ref<refl::Function> old_specialization = generic->specialize_with_arg_types({float_type});
REQUIRE(old_specialization);
CHECK(old_specialization->return_type() == float_type);
uint64_t generation = layout->generation();

layout->on_hot_reload(module_b->layout());
Expand All @@ -291,6 +313,9 @@ int get_value(Foo foo) { return foo.value; }
CHECK(new_function.get() == old_function.get());
CHECK(old_function->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32));
CHECK(new_function->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32));
CHECK(old_overload->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32));
CHECK(old_nested_specialization->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32));
CHECK(old_specialization->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32));
}

TEST_SUITE_END();
Loading