Skip to content

Commit

Permalink
Add InstanceStruct test data generation helper and unit test (#546)
Browse files Browse the repository at this point in the history
* CodegenLLVMHelperVisitor improved without hardcoded parameters
* Added get_instance_struct_ptr to get instance structure for variable information
* test/unit/codegen/codegen_data_helper.cpp : first draft implementation
   of codegen data helper
* Added test for typecasting to the proper struct type

Co-authored-by: Pramod Kumbhar <pramod.s.kumbhar@gmail.com>
  • Loading branch information
iomaganaris and pramodk committed Sep 15, 2022
1 parent fd2053e commit 57cb77d
Show file tree
Hide file tree
Showing 8 changed files with 512 additions and 13 deletions.
25 changes: 19 additions & 6 deletions src/codegen/llvm/codegen_llvm_helper_visitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ namespace codegen {

using namespace fmt::literals;

/// initialize static member variables
const ast::AstNodeType CodegenLLVMHelperVisitor::INTEGER_TYPE = ast::AstNodeType::INTEGER;
const ast::AstNodeType CodegenLLVMHelperVisitor::FLOAT_TYPE = ast::AstNodeType::DOUBLE;
const std::string CodegenLLVMHelperVisitor::NODECOUNT_VAR = "node_count";
const std::string CodegenLLVMHelperVisitor::VOLTAGE_VAR = "voltage";
const std::string CodegenLLVMHelperVisitor::NODE_INDEX_VAR = "node_index";

/**
* \brief Create variable definition statement
*
Expand Down Expand Up @@ -157,7 +164,12 @@ void CodegenLLVMHelperVisitor::create_function_for_node(ast::Block& node) {
auto function = std::make_shared<ast::CodegenFunction>(fun_ret_type, name, arguments, block);
codegen_functions.push_back(function);
}

/**
* \note : Order of variables is not important but we assume all pointers
* are added first and then scalar variables like t, dt, second_order etc.
* This order is assumed when we allocate data for integration testing
* and benchmarking purpose. See CodegenDataHelper::create_data().
*/
std::shared_ptr<ast::InstanceStruct> CodegenLLVMHelperVisitor::create_instance_struct() {
ast::CodegenVarWithTypeVector codegen_vars;

Expand Down Expand Up @@ -186,15 +198,15 @@ std::shared_ptr<ast::InstanceStruct> CodegenLLVMHelperVisitor::create_instance_s
}

// add voltage and node index
add_var_with_type("voltage", FLOAT_TYPE, /*is_pointer=*/1);
add_var_with_type("node_index", INTEGER_TYPE, /*is_pointer=*/1);
add_var_with_type(VOLTAGE_VAR, FLOAT_TYPE, /*is_pointer=*/1);
add_var_with_type(NODE_INDEX_VAR, INTEGER_TYPE, /*is_pointer=*/1);

// add dt, t, celsius
add_var_with_type(naming::NTHREAD_T_VARIABLE, FLOAT_TYPE, /*is_pointer=*/0);
add_var_with_type(naming::NTHREAD_DT_VARIABLE, FLOAT_TYPE, /*is_pointer=*/0);
add_var_with_type(naming::CELSIUS_VARIABLE, FLOAT_TYPE, /*is_pointer=*/0);
add_var_with_type(naming::SECOND_ORDER_VARIABLE, INTEGER_TYPE, /*is_pointer=*/0);
add_var_with_type(MECH_NODECOUNT_VAR, INTEGER_TYPE, /*is_pointer=*/0);
add_var_with_type(NODECOUNT_VAR, INTEGER_TYPE, /*is_pointer=*/0);

return std::make_shared<ast::InstanceStruct>(codegen_vars);
}
Expand Down Expand Up @@ -510,7 +522,7 @@ void CodegenLLVMHelperVisitor::visit_nrn_state_block(ast::NrnStateBlock& node) {

/// loop constructs : initialization, condition and increment
const auto& initialization = loop_initialization_expression(INDUCTION_VAR);
const auto& condition = create_expression("{} < {}"_format(INDUCTION_VAR, MECH_NODECOUNT_VAR));
const auto& condition = create_expression("{} < {}"_format(INDUCTION_VAR, NODECOUNT_VAR));
const auto& increment = loop_increment_expression(INDUCTION_VAR, vector_width);

/// loop body : initialization + solve blocks
Expand All @@ -524,7 +536,8 @@ void CodegenLLVMHelperVisitor::visit_nrn_state_block(ast::NrnStateBlock& node) {
/// access node index and corresponding voltage
loop_index_statements.push_back(
visitor::create_statement("node_id = node_index[{}]"_format(INDUCTION_VAR)));
loop_body_statements.push_back(visitor::create_statement("v = voltage[node_id]"));
loop_body_statements.push_back(
visitor::create_statement("v = {}[node_id]"_format(VOLTAGE_VAR)));

/// read ion variables
ion_read_statements(BlockType::State,
Expand Down
14 changes: 9 additions & 5 deletions src/codegen/llvm/codegen_llvm_helper_visitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,8 @@ class CodegenLLVMHelperVisitor: public visitor::AstVisitor {
/// mechanism data helper
InstanceVarHelper instance_var_helper;

/// default integer and float node type
const ast::AstNodeType INTEGER_TYPE = ast::AstNodeType::INTEGER;
const ast::AstNodeType FLOAT_TYPE = ast::AstNodeType::DOUBLE;

/// name of the mechanism instance parameter
const std::string MECH_INSTANCE_VAR = "mech";
const std::string MECH_NODECOUNT_VAR = "node_count";

/// name of induction variable used in the kernel.
const std::string INDUCTION_VAR = "id";
Expand All @@ -130,6 +125,15 @@ class CodegenLLVMHelperVisitor: public visitor::AstVisitor {
std::shared_ptr<ast::InstanceStruct> create_instance_struct();

public:
/// default integer and float node type
static const ast::AstNodeType INTEGER_TYPE;
static const ast::AstNodeType FLOAT_TYPE;

// node count, voltage and node index variables
static const std::string NODECOUNT_VAR;
static const std::string VOLTAGE_VAR;
static const std::string NODE_INDEX_VAR;

CodegenLLVMHelperVisitor(int vector_width)
: vector_width(vector_width){};

Expand Down
4 changes: 4 additions & 0 deletions src/codegen/llvm/codegen_llvm_visitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ llvm::Value* CodegenLLVMVisitor::get_variable_ptr(const ast::VarName& node) {
return ptr;
}

std::shared_ptr<ast::InstanceStruct> CodegenLLVMVisitor::get_instance_struct_ptr() {
return instance_var_helper.instance;
}

void CodegenLLVMVisitor::run_llvm_opt_passes() {
/// run some common optimisation passes that are commonly suggested
fpm.add(llvm::createInstructionCombiningPass());
Expand Down
6 changes: 6 additions & 0 deletions src/codegen/llvm/codegen_llvm_visitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ class CodegenLLVMVisitor: public visitor::ConstAstVisitor {
*/
llvm::Value* get_variable_ptr(const ast::VarName& node);

/**
* Returns shared_ptr to generated ast::InstanceStruct
* \return std::shared_ptr<ast::InstanceStruct>
*/
std::shared_ptr<ast::InstanceStruct> get_instance_struct_ptr();

/**
* Create a function call to an external method
* \param name external method name
Expand Down
5 changes: 3 additions & 2 deletions test/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ target_link_libraries(
${NMODL_WRAPPER_LIBS})

if(NMODL_ENABLE_LLVM)
include_directories(${LLVM_INCLUDE_DIRS})
add_executable(testllvm visitor/main.cpp codegen/codegen_llvm_ir.cpp)
include_directories(${LLVM_INCLUDE_DIRS} codegen)
add_executable(testllvm visitor/main.cpp codegen/codegen_llvm_ir.cpp
codegen/codegen_data_helper.cpp codegen/codegen_llvm_instance_struct.cpp)
add_executable(test_llvm_runner visitor/main.cpp codegen/codegen_llvm_execution.cpp)
target_link_libraries(
testllvm
Expand Down
186 changes: 186 additions & 0 deletions test/unit/codegen/codegen_data_helper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#include <algorithm>

#include "ast/codegen_var_type.hpp"
#include "codegen/llvm/codegen_llvm_helper_visitor.hpp"

#include "codegen_data_helper.hpp"

namespace nmodl {
namespace codegen {

// scalar variables with default values
const double default_nthread_dt_value = 0.025;
const double default_nthread_t_value = 100.0;
const double default_celsius_value = 34.0;
const int default_second_order_value = 0;

// cleanup all members and struct base pointer
CodegenInstanceData::~CodegenInstanceData() {
// first free num_ptr_members members which are pointers
for (size_t i = 0; i < num_ptr_members; i++) {
free(members[i]);
}
// and then pointer to container struct
free(base_ptr);
}

/**
* \todo : various things can be improved here
* - if variable is voltage then initialization range could be -65 to +65
* - if variable is double or float then those could be initialize with
* "some" floating point value between range like 1.0 to 100.0. Note
* it would be nice to have unique values to avoid errors like division
* by zero. We have simple implementation that is taking care of this.
* - if variable is integer then initialization range must be between
* 0 and num_elements. In practice, num_elements is number of instances
* of a particular mechanism. This would be <= number of compartments
* in the cell. For now, just initialize integer variables from 0 to
* num_elements - 1.
*/
void initialize_variable(const std::shared_ptr<ast::CodegenVarWithType>& var,
void* ptr,
size_t initial_value,
size_t num_elements) {
ast::AstNodeType type = var->get_type()->get_type();
const std::string& name = var->get_name()->get_node_name();

if (type == ast::AstNodeType::DOUBLE) {
const auto& generated_double_data = generate_dummy_data<double>(initial_value,
num_elements);
double* data = (double*) ptr;
for (size_t i = 0; i < num_elements; i++) {
data[i] = generated_double_data[i];
}
} else if (type == ast::AstNodeType::FLOAT) {
const auto& generated_float_data = generate_dummy_data<float>(initial_value, num_elements);
float* data = (float*) ptr;
for (size_t i = 0; i < num_elements; i++) {
data[i] = generated_float_data[i];
}
} else if (type == ast::AstNodeType::INTEGER) {
const auto& generated_int_data = generate_dummy_data<int>(initial_value, num_elements);
int* data = (int*) ptr;
for (size_t i = 0; i < num_elements; i++) {
data[i] = generated_int_data[i];
}
} else {
throw std::runtime_error("Unhandled data type during initialize_variable");
};
}

CodegenInstanceData CodegenDataHelper::create_data(size_t num_elements, size_t seed) {
// alignment with 64-byte to generate aligned loads/stores
const unsigned NBYTE_ALIGNMENT = 64;

// get variable information
const auto& variables = instance->get_codegen_vars();

// start building data
CodegenInstanceData data;
data.num_elements = num_elements;

// base pointer to instance object
void* base = nullptr;

// max size of each member : pointer / double has maximum size
size_t member_size = std::max(sizeof(double), sizeof(double*));

// allocate instance object with memory alignment
posix_memalign(&base, NBYTE_ALIGNMENT, member_size * variables.size());
data.base_ptr = base;

size_t offset = 0;
void* ptr = base;
size_t variable_index = 0;

// allocate each variable and allocate memory at particular offset in base pointer
for (auto& var: variables) {
// only process until first non-pointer variable
if (!var->get_is_pointer()) {
break;
}

// check type of variable and it's size
size_t member_size = 0;
ast::AstNodeType type = var->get_type()->get_type();
if (type == ast::AstNodeType::DOUBLE) {
member_size = sizeof(double);
} else if (type == ast::AstNodeType::FLOAT) {
member_size = sizeof(float);
} else if (type == ast::AstNodeType::INTEGER) {
member_size = sizeof(int);
}

// allocate memory and setup a pointer
void* member;
posix_memalign(&member, NBYTE_ALIGNMENT, member_size * num_elements);
initialize_variable(var, member, variable_index, num_elements);

// copy address at specific location in the struct
memcpy(ptr, &member, sizeof(double*));

data.offsets.push_back(offset);
data.members.push_back(member);
data.num_ptr_members++;

// all pointer types are of same size, so just use double*
offset += sizeof(double*);
ptr = (char*) base + offset;

variable_index++;
}

// we are now switching from pointer type to next member type (e.g. double)
// ideally we should use padding but switching from double* to double should
// already meet alignment requirements
for (auto& var: variables) {
// process only scalar elements
if (var->get_is_pointer()) {
continue;
}
ast::AstNodeType type = var->get_type()->get_type();
const std::string& name = var->get_name()->get_node_name();

// some default values for standard parameters
double value = 0;
if (name == naming::NTHREAD_DT_VARIABLE) {
value = default_nthread_dt_value;
} else if (name == naming::NTHREAD_T_VARIABLE) {
value = default_nthread_t_value;
} else if (name == naming::CELSIUS_VARIABLE) {
value = default_celsius_value;
} else if (name == CodegenLLVMHelperVisitor::NODECOUNT_VAR) {
value = num_elements;
} else if (name == naming::SECOND_ORDER_VARIABLE) {
value = default_second_order_value;
}

if (type == ast::AstNodeType::DOUBLE) {
*((double*) ptr) = value;
data.offsets.push_back(offset);
data.members.push_back(ptr);
offset += sizeof(double);
ptr = (char*) base + offset;
} else if (type == ast::AstNodeType::FLOAT) {
*((float*) ptr) = float(value);
data.offsets.push_back(offset);
data.members.push_back(ptr);
offset += sizeof(float);
ptr = (char*) base + offset;
} else if (type == ast::AstNodeType::INTEGER) {
*((int*) ptr) = int(value);
data.offsets.push_back(offset);
data.members.push_back(ptr);
offset += sizeof(int);
ptr = (char*) base + offset;
} else {
throw std::runtime_error(
"Unhandled type while allocating data in CodegenDataHelper::create_data()");
}
}

return data;
}

} // namespace codegen
} // namespace nmodl
Loading

0 comments on commit 57cb77d

Please sign in to comment.