Skip to content

Commit

Permalink
Implement ExternFunction initialization from XML
Browse files Browse the repository at this point in the history
The implementation follows
the MEF proposal for "define-extern-function".
This allows any extern C function to be declared in the MEF XML
and used later in Expression.

The downside is exponential explosion of possible function-interfaces
with respect to allowed parameter types and numbers.
In particular, the implementation relies on
C++ template meta-programming,
which heavily slows the build times
and increases build memory consumption.

The binary size does not increase much with the current implementation.

Validation:
    - Duplicate declarations
    - Empty return type
    - Invalid number of parameters exceeding the maximum number
    - Undefined extern library to lookup the symbol
    - Undefined extern-function symbol in the reference library

Issue #74
  • Loading branch information
rakhimov committed Aug 21, 2017
1 parent 01ba2f7 commit 018ad3f
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 0 deletions.
153 changes: 153 additions & 0 deletions src/initializer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,18 @@ void Initializer::Define(const xmlpp::Element* xml_node, Alignment* alignment) {
/// @}

void Initializer::ProcessTbdElements() {
for (const auto& entry : doc_to_file_) {
for (const xmlpp::Node* node :
entry.first->find("./define-extern-function")) {
try {
DefineExternFunction(XmlElement(node));
} catch (ValidationError& err) {
err.msg("In file '" + entry.second + "', " + err.msg());
throw;
}
}
}

for (const auto& tbd_element : tbd_) {
try {
boost::apply_visitor(
Expand Down Expand Up @@ -1340,6 +1352,147 @@ Formula::EventArg Initializer::GetEvent(const std::string& entity_reference,

#undef GET_EVENT

namespace { // Extern function initialization helpers.

/// All the allowed extern function parameter types.
///
/// @note Template code may be optimized for these types only.
enum class ExternParamType { kInt = 1, kDouble };
const int kExternTypeBase = 3; ///< The information base for encoding.
const int kMaxNumParam = 5; ///< The max number of args (excludes the return).
const int kNumInterfaces = 126; ///< All possible interfaces.

/// Encodes parameter types kExternTypeBase base number.
///
/// @param[in] args The non-empty set XML elements encoding parameter types.
///
/// @returns Unique integer encoding parameter types.
///
/// @pre The number of parameters is less than log_base(max int).
int Encode(const xmlpp::NodeSet& args) noexcept {
assert(!args.empty() && args.size() < 19);
auto to_digit = [](xmlpp::Node* node) -> int {
std::string name = node->get_name();
return static_cast<int>([&name] {
if (name == "int")
return ExternParamType::kInt;
assert(name == "double");
return ExternParamType::kDouble;
}());
};

int ret = 0;
int base_power = 1; // Base ^ (pos - 1).
for (xmlpp::Node* node : args) {
ret += base_power * to_digit(node);
base_power *= kExternTypeBase;
}

return ret;
}

/// Encodes function parameter types at compile-time.
/// @{
template <typename T, typename... Ts>
constexpr int Encode(int base_power = 1) noexcept {
return Encode<T>(base_power) + Encode<Ts...>(base_power * kExternTypeBase);
}

template <>
constexpr int Encode<int>(int base_power) noexcept {
return base_power * static_cast<int>(ExternParamType::kInt);
}

template <>
constexpr int Encode<double>(int base_power) noexcept {
return base_power * static_cast<int>(ExternParamType::kDouble);
}
/// @}

using ExternFunctionExtractor = ExternFunctionPtr (*)(std::string,
const std::string&,
const ExternLibrary&);
using ExternFunctionExtractorMap =
std::unordered_map<int, ExternFunctionExtractor>;

/// @tparam N The number of parameters.
template <int N>
struct ExternFunctionGenerator;

template <>
struct ExternFunctionGenerator<0> {
template <typename... Ts>
static void Generate(ExternFunctionExtractorMap* function_map) noexcept {
///< @todo GCC 4.9, 5.4 segfaults on move for lambda arguments.
struct Extractor { // Use instead of lambda!
static ExternFunctionPtr Extract(std::string name,
const std::string& symbol,
const ExternLibrary& library) {
return std::make_unique<ExternFunction<Ts...>>(std::move(name), symbol,
library);
}
};
function_map->emplace(Encode<Ts...>(), &Extractor::Extract);
}
};

template <int N>
struct ExternFunctionGenerator {
template <typename... Ts>
static void Generate(ExternFunctionExtractorMap* function_map) noexcept {
ExternFunctionGenerator<0>::template Generate<Ts...>(function_map);
ExternFunctionGenerator<N - 1>::template Generate<Ts..., int>(function_map);
ExternFunctionGenerator<N - 1>::template Generate<Ts..., double>(
function_map);
}
};

} // namespace

void Initializer::DefineExternFunction(const xmlpp::Element* xml_element) {
static const ExternFunctionExtractorMap function_extractors = [] {
ExternFunctionExtractorMap function_map;
function_map.reserve(kNumInterfaces);
ExternFunctionGenerator<kMaxNumParam>::Generate<int>(&function_map);
ExternFunctionGenerator<kMaxNumParam>::Generate<double>(&function_map);
assert(function_map.size() == kNumInterfaces);
return function_map;
}();

const ExternLibrary& library = [this, xml_element]() -> decltype(auto) {
std::string lib_name = GetAttributeValue(xml_element, "library");
auto it = model_->libraries().find(lib_name);
if (it == model_->libraries().end())
throw ValidationError(GetLine(xml_element) +
"Undefined extern library: " + lib_name);
return **it;
}();

ExternFunctionPtr extern_function = [xml_element, &library] {
xmlpp::NodeSet args = GetNonAttributeElements(xml_element);
assert(!args.empty());
int num_args = args.size() - /*return*/ 1;
if (num_args > kMaxNumParam) {
throw ValidationError(GetLine(xml_element) +
"The number of function parameters '" +
std::to_string(num_args) +
"' exceeds the number of allowed parameters '" +
std::to_string(kMaxNumParam) + "'");
}
int encoding = Encode(args);
std::string symbol = GetAttributeValue(xml_element, "symbol");
std::string name = GetAttributeValue(xml_element, "name");
try {
return function_extractors.at(encoding)(std::move(name), symbol, library);
} catch (ValidationError& err) {
err.msg(GetLine(xml_element) + err.msg());
throw;
}
}();

Register(std::move(extern_function), xml_element);
}

void Initializer::ValidateInitialization() {
// Check if *all* gates have no cycles.
cycle::CheckCycle<Gate>(model_->gates(), "gate");
Expand Down
9 changes: 9 additions & 0 deletions src/initializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,15 @@ class Initializer : private boost::noncopyable {
const std::string& base_path, const IdTable<P>& container,
const PathTable<T>& path_container);

/// Defines extern function.
///
/// @param[in] xml_element The XML element with the data.
///
/// @throws ValidationError The initialization contains validity errors.
///
/// @pre All libraries are defined.
void DefineExternFunction(const xmlpp::Element* xml_element);

/// Validates if the initialization of the analysis is successful.
///
/// @throws CycleError Model contains cycles.
Expand Down
6 changes: 6 additions & 0 deletions src/model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ void Model::Add(std::unique_ptr<ExternLibrary> library) {
"Redefinition of extern library: ");
}

void Model::Add(ExternFunctionPtr extern_function) {
mef::AddElement<RedefinitionError>(std::move(extern_function),
&extern_functions_,
"Redefinition of extern function: ");
}

Formula::EventArg Model::GetEvent(const std::string& id) {
if (auto it = ext::find(basic_events(), id))
return it->get();
Expand Down
5 changes: 5 additions & 0 deletions src/model.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ class Model : public Element, private boost::noncopyable {
const ElementTable<std::unique_ptr<ExternLibrary>>& libraries() const {
return libraries_;
}
const ElementTable<ExternFunctionPtr>& extern_functions() const {
return extern_functions_;
}
/// @}

/// Adds MEF constructs into the model container.
Expand Down Expand Up @@ -126,6 +129,7 @@ class Model : public Element, private boost::noncopyable {
instructions_.emplace_back(std::move(element));
}
void Add(std::unique_ptr<ExternLibrary> element);
void Add(ExternFunctionPtr element);
/// @}

/// Convenience function to retrieve an event with its ID.
Expand Down Expand Up @@ -172,6 +176,7 @@ class Model : public Element, private boost::noncopyable {
IdTable<BasicEventPtr> basic_events_;
IdTable<ParameterPtr> parameters_;
ElementTable<std::unique_ptr<ExternLibrary>> libraries_;
ElementTable<ExternFunctionPtr> extern_functions_;
std::unique_ptr<MissionTime> mission_time_;
IdTable<CcfGroupPtr> ccf_groups_;
std::vector<std::unique_ptr<Expression>> expressions_;
Expand Down
6 changes: 6 additions & 0 deletions tests/initializer_tests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ TEST(InitializerTest, CorrectModelInputs) {
std::string dir = "./share/scram/input/model/";
const char* correct_inputs[] = {
"extern_library.xml",
"extern_function.xml",
"valid_alignment.xml",
"valid_sum_alignment.xml",
"private_phases.xml"};
Expand All @@ -324,6 +325,11 @@ TEST(InitializerTest, IncorrectModelInputs) {

const char* incorrect_inputs[] = {
"duplicate_extern_libraries.xml",
"duplicate_extern_functions.xml",
"undefined_extern_library.xml",
"undefined_symbol_extern_function.xml",
"invalid_num_param_extern_function.xml",
"empty_extern_function.xml",
"extern_library_invalid_path_format.xml",
"extern_library_ioerror.xml",
"duplicate_phases.xml",
Expand Down
10 changes: 10 additions & 0 deletions tests/input/model/duplicate_extern_functions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<opsa-mef>
<define-extern-library name="dummy" path="../../../../lib/scram/test/scram_dummy_extern" decorate="true"/>
<define-extern-function name="fun" symbol="foo" library="dummy">
<int/>
</define-extern-function>
<define-extern-function name="fun" symbol="bar" library="dummy">
<double/>
</define-extern-function>
</opsa-mef>
5 changes: 5 additions & 0 deletions tests/input/model/empty_extern_function.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<opsa-mef>
<define-extern-library name="dummy" path="../../../../lib/scram/test/scram_dummy_extern" decorate="true"/>
<define-extern-function name="fun1" symbol="foo" library="dummy"/>
</opsa-mef>
14 changes: 14 additions & 0 deletions tests/input/model/extern_function.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<opsa-mef>
<define-extern-library name="dummy" path="../../../../lib/scram/test/scram_dummy_extern" decorate="true"/>
<define-extern-function name="fun1" symbol="foo" library="dummy">
<int/>
</define-extern-function>
<define-extern-function name="fun2" symbol="bar" library="dummy">
<double/>
</define-extern-function>
<define-extern-function name="fun3" symbol="identity" library="dummy">
<double/>
<double/>
</define-extern-function>
</opsa-mef>
13 changes: 13 additions & 0 deletions tests/input/model/invalid_num_param_extern_function.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<opsa-mef>
<define-extern-library name="dummy" path="../../../../lib/scram/test/scram_dummy_extern" decorate="true"/>
<define-extern-function name="fun1" symbol="foo" library="dummy">
<double/>
<double/>
<double/>
<double/>
<double/>
<double/>
<double/>
</define-extern-function>
</opsa-mef>
6 changes: 6 additions & 0 deletions tests/input/model/undefined_extern_library.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0"?>
<opsa-mef>
<define-extern-function name="fun1" symbol="foo" library="dummy">
<int/>
</define-extern-function>
</opsa-mef>
7 changes: 7 additions & 0 deletions tests/input/model/undefined_symbol_extern_function.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<opsa-mef>
<define-extern-library name="dummy" path="../../../../lib/scram/test/scram_dummy_extern" decorate="true"/>
<define-extern-function name="fun" symbol="fun" library="dummy">
<int/>
</define-extern-function>
</opsa-mef>

0 comments on commit 018ad3f

Please sign in to comment.