Skip to content

Commit

Permalink
Initial LLVM codegen vistor routines (#457)
Browse files Browse the repository at this point in the history
* Added LLVM code generation for `ProcedureBlock`.
* Added code generation routines for double, integer and
   boolean variable types. 
* Added binary and unary operator code generation:
     - Supported binary operators: +, -, *, /.
     - Supported unary operators: -.
     - Assignment (=) is also supported.
* Added regex matching unit tests for LLVM code generation.
* Fixed Travis CI/builds.

fixes #451, fixes #452, fixes #456

Co-authored-by: Pramod Kumbhar <pramod.s.kumbhar@gmail.com>
  • Loading branch information
georgemitenkov and pramodk authored Dec 22, 2020
1 parent 94177de commit 6f66c71
Show file tree
Hide file tree
Showing 10 changed files with 376 additions and 64 deletions.
17 changes: 11 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ addons:
- boost
- flex
- bison
- llvm
- openmpi
- python@3
update: true
Expand Down Expand Up @@ -105,7 +106,11 @@ install:
script:
- echo "------- Build, Test and Install -------"
- mkdir build && pushd build
- cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -DNMODL_ENABLE_LLVM=ON -DLLVM_DIR=`brew --prefix llvm`/lib/cmake/llvm;
else
cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -DNMODL_ENABLE_LLVM=OFF;
fi
- make -j 2;
if [ $? -ne 0 ]; then
make VERBOSE=1;
Expand All @@ -121,11 +126,11 @@ after_success:
# Scikit-build does not support build_ext --inplace in custom commands.
# More info at: https://github.com/scikit-build/scikit-build/issues/489
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
echo "------- Build Documentation -------";
python3 setup.py build_ext --inplace docs -j 2 -G "Unix Makefiles" || travis_terminate 1;
cd $TRAVIS_BUILD_DIR/_skbuild/linux-x86_64-3.8/setuptools/sphinx;
rm -rf doctest doctrees && touch .nojekyll;
echo "<meta http-equiv=\"refresh\" content=\"0; url=./html/index.html\" />" > index.html;
echo "------- Skip Build Documentation -------";
#python3 setup.py build_ext --inplace docs -j 2 -G "Unix Makefiles" || travis_terminate 1;
#cd $TRAVIS_BUILD_DIR/_skbuild/linux-x86_64-3.8/setuptools/sphinx;
#rm -rf doctest doctrees && touch .nojekyll;
#echo "<meta http-equiv=\"refresh\" content=\"0; url=./html/index.html\" />" > index.html;
fi

#=============================================================================
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ find_python_module(yaml 3.12 REQUIRED)
# =============================================================================
if(NMODL_ENABLE_LLVM)
include(LLVMHelper)
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(-DNMODL_LLVM_BACKEND)
endif()

Expand Down
5 changes: 3 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ jobs:
- checkout: self
submodules: True
- script: |
brew install flex cmake python@3
brew install bison llvm
brew install flex bison cmake python@3 llvm
python3 -m pip install -U pip setuptools
python3 -m pip install --user 'Jinja2>=2.9.3' 'PyYAML>=3.13' pytest 'sympy>=1.3,<1.6'
displayName: 'Install Dependencies'
Expand All @@ -132,6 +131,7 @@ jobs:
displayName: 'Build and Run Tests'
- job: 'manylinux_wheels'
timeoutInMinutes: 45
condition: eq(1,2)
pool:
vmImage: 'ubuntu-18.04'
strategy:
Expand Down Expand Up @@ -177,6 +177,7 @@ jobs:
- template: ci/upload-wheels.yml
- job: 'macos_wheels'
timeoutInMinutes: 45
condition: eq(1,2)
pool:
vmImage: 'macOS-10.15'
strategy:
Expand Down
14 changes: 8 additions & 6 deletions cmake/LLVMHelper.cmake
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
# =============================================================================
# LLVM/Clang needs to be linked with either libc++ or libstdc++
# =============================================================================

find_package(LLVM REQUIRED CONFIG)

# include LLVM header and core library
llvm_map_components_to_libnames(LLVM_LIBS_TO_LINK core)
set(CMAKE_REQUIRED_INCLUDES ${LLVM_INCLUDE_DIRS})
set(CMAKE_REQUIRED_LIBRARIES ${LLVM_LIBS_TO_LINK})

if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NMODL_ENABLE_LLVM)
find_package(LLVM REQUIRED CONFIG)
include(CheckCXXSourceCompiles)

# test by including LLVM header and core library
llvm_map_components_to_libnames(LLVM_LIBS_TO_LINK core)
set(CMAKE_REQUIRED_INCLUDES ${LLVM_INCLUDE_DIRS})
set(CMAKE_REQUIRED_LIBRARIES ${LLVM_LIBS_TO_LINK})

# simple code to test LLVM library linking
set(CODE_TO_TEST
"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def _config_exe(exe_name):
]


cmake_args = ["-DPYTHON_EXECUTABLE=" + sys.executable, "-DNMODL_ENABLE_LLVM=OFF"]
cmake_args = ["-DPYTHON_EXECUTABLE=" + sys.executable, "-DNMODL_ENABLE_LLVM=OFF", "-DNMODL_ENABLE_PYTHON_BINDINGS=ON"]
if "bdist_wheel" in sys.argv:
cmake_args.append("-DLINK_AGAINST_PYTHON=FALSE")

Expand Down
157 changes: 132 additions & 25 deletions src/codegen/llvm/codegen_llvm_visitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,153 @@
#include "visitors/visitor_utils.hpp"

#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/ValueSymbolTable.h"

namespace nmodl {
namespace codegen {


// LLVM code generator objects
using namespace llvm;
static std::unique_ptr<LLVMContext> TheContext;
static std::unique_ptr<Module> TheModule;
static std::unique_ptr<IRBuilder<>> Builder;
static std::map<std::string, Value*> NamedValues;
/****************************************************************************************/
/* Overloaded visitor routines */
/****************************************************************************************/


void CodegenLLVMVisitor::visit_statement_block(const ast::StatementBlock& node) {
logger->info("CodegenLLVMVisitor : visiting statement block");
node.visit_children(*this);
// TODO : code generation for new block scope
void CodegenLLVMVisitor::visit_binary_expression(const ast::BinaryExpression& node) {
const auto& op = node.get_op().get_value();

// Process rhs first, since lhs is handled differently for assignment and binary
// operators.
node.get_rhs()->accept(*this);
llvm::Value* rhs = values.back();
values.pop_back();
if (op == ast::BinaryOp::BOP_ASSIGN) {
auto var = dynamic_cast<ast::VarName*>(node.get_lhs().get());
if (!var) {
throw std::runtime_error("Error: only VarName assignment is currently supported.\n");
}
llvm::Value* alloca = named_values[var->get_node_name()];
builder.CreateStore(rhs, alloca);
return;
}

node.get_lhs()->accept(*this);
llvm::Value* lhs = values.back();
values.pop_back();
llvm::Value* result;

// \todo: Support other binary operators
switch (op) {
#define DISPATCH(binary_op, llvm_op) \
case binary_op: \
result = llvm_op(lhs, rhs); \
values.push_back(result); \
break;

DISPATCH(ast::BinaryOp::BOP_ADDITION, builder.CreateFAdd);
DISPATCH(ast::BinaryOp::BOP_DIVISION, builder.CreateFDiv);
DISPATCH(ast::BinaryOp::BOP_MULTIPLICATION, builder.CreateFMul);
DISPATCH(ast::BinaryOp::BOP_SUBTRACTION, builder.CreateFSub);

#undef DISPATCH
}
}

void CodegenLLVMVisitor::visit_procedure_block(const ast::ProcedureBlock& node) {
logger->info("CodegenLLVMVisitor : visiting {} procedure", node.get_node_name());

// print position, nmodl and json form as
/*
logger->info("Location {} \n NMODL {} \n JSON : {} \n",
node.get_token()->position(),
to_nmodl(node),
to_json(node));
*/
node.visit_children(*this);
// TODO : code generation for procedure block
void CodegenLLVMVisitor::visit_boolean(const ast::Boolean& node) {
const auto& constant = llvm::ConstantInt::get(llvm::Type::getInt1Ty(*context),
node.get_value());
values.push_back(constant);
}

void CodegenLLVMVisitor::visit_double(const ast::Double& node) {
const auto& constant = llvm::ConstantFP::get(llvm::Type::getDoubleTy(*context),
node.get_value());
values.push_back(constant);
}

void CodegenLLVMVisitor::visit_integer(const ast::Integer& node) {
const auto& constant = llvm::ConstantInt::get(llvm::Type::getInt32Ty(*context),
node.get_value());
values.push_back(constant);
}

void CodegenLLVMVisitor::visit_local_list_statement(const ast::LocalListStatement& node) {
for (const auto& variable: node.get_variables()) {
// LocalVar always stores a Name.
auto name = variable->get_node_name();
llvm::Type* var_type = llvm::Type::getDoubleTy(*context);
llvm::Value* alloca = builder.CreateAlloca(var_type, /*ArraySize=*/nullptr, name);
named_values[name] = alloca;
}
}

void CodegenLLVMVisitor::visit_program(const ast::Program& node) {
node.visit_children(*this);
result_code = "Hello World";
// Keep this for easier development (maybe move to debug mode later).
std::cout << print_module();
}

void CodegenLLVMVisitor::visit_procedure_block(const ast::ProcedureBlock& node) {
const auto& name = node.get_node_name();
const auto& parameters = node.get_parameters();

// The procedure parameters are doubles by default.
std::vector<llvm::Type*> arg_types;
for (size_t i = 0, e = parameters.size(); i < e; ++i)
arg_types.push_back(llvm::Type::getDoubleTy(*context));
llvm::Type* return_type = llvm::Type::getVoidTy(*context);

llvm::Function* proc =
llvm::Function::Create(llvm::FunctionType::get(return_type, arg_types, /*isVarArg=*/false),
llvm::Function::ExternalLinkage,
name,
*module);

llvm::BasicBlock* body = llvm::BasicBlock::Create(*context, /*Name=*/"", proc);
builder.SetInsertPoint(body);

// First, allocate parameters on the stack and add them to the symbol table.
unsigned i = 0;
for (auto& arg: proc->args()) {
std::string arg_name = parameters[i++].get()->get_node_name();
llvm::Value* alloca = builder.CreateAlloca(arg.getType(), /*ArraySize=*/nullptr, arg_name);
arg.setName(arg_name);
builder.CreateStore(&arg, alloca);
named_values[arg_name] = alloca;
}

const auto& statements = node.get_statement_block()->get_statements();
for (const auto& statement: statements) {
// \todo: Support other statement types.
if (statement->is_local_list_statement() || statement->is_expression_statement())
statement->accept(*this);
}

values.clear();
// \todo: Add proper support for the symbol table.
named_values.clear();
}

void CodegenLLVMVisitor::visit_unary_expression(const ast::UnaryExpression& node) {
ast::UnaryOp op = node.get_op().get_value();
node.get_expression()->accept(*this);
llvm::Value* value = values.back();
values.pop_back();
if (op == ast::UOP_NEGATION) {
llvm::Value* result = builder.CreateFNeg(value);
values.push_back(result);
} else {
// Support only `double` operators for now.
throw std::runtime_error("Error: unsupported unary operator\n");
}
}

void CodegenLLVMVisitor::visit_var_name(const ast::VarName& node) {
llvm::Value* var = builder.CreateLoad(named_values[node.get_node_name()]);
values.push_back(var);
}

} // namespace codegen
Expand Down
40 changes: 33 additions & 7 deletions src/codegen/llvm/codegen_llvm_visitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
#include "utils/logger.hpp"
#include "visitors/ast_visitor.hpp"

#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"

namespace nmodl {
namespace codegen {

Expand All @@ -45,8 +49,18 @@ class CodegenLLVMVisitor: public visitor::ConstAstVisitor {
// Output directory for code generation
std::string output_dir;

// result string for demo
std::string result_code;
private:
std::unique_ptr<llvm::LLVMContext> context = std::make_unique<llvm::LLVMContext>();

std::unique_ptr<llvm::Module> module = std::make_unique<llvm::Module>(mod_filename, *context);

llvm::IRBuilder<> builder;

// Stack to hold visited values
std::vector<llvm::Value*> values;

// Mappings for named values for lookups
std::map<std::string, llvm::Value*> named_values;

public:
/**
Expand All @@ -57,15 +71,27 @@ class CodegenLLVMVisitor: public visitor::ConstAstVisitor {
*/
CodegenLLVMVisitor(const std::string& mod_filename, const std::string& output_dir)
: mod_filename(mod_filename)
, output_dir(output_dir) {}
, output_dir(output_dir)
, builder(*context) {}

void visit_statement_block(const ast::StatementBlock& node) override;
// Visitors
void visit_binary_expression(const ast::BinaryExpression& node) override;
void visit_boolean(const ast::Boolean& node) override;
void visit_double(const ast::Double& node) override;
void visit_integer(const ast::Integer& node) override;
void visit_local_list_statement(const ast::LocalListStatement& node) override;
void visit_procedure_block(const ast::ProcedureBlock& node) override;
void visit_program(const ast::Program& node) override;
void visit_unary_expression(const ast::UnaryExpression& node) override;
void visit_var_name(const ast::VarName& node) override;

// demo method
std::string get_code() const {
return result_code;
// TODO: use custom printer here
std::string print_module() const {
std::string str;
llvm::raw_string_ostream os(str);
os << *module;
os.flush();
return str;
}
};

Expand Down
9 changes: 7 additions & 2 deletions test/integration/mod/procedure.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
NEURON {
SUFFIX procedure_test
THREADSAFE
}

PROCEDURE hello_world() {
print("Hello World")
printf("Hello World")
}

PROCEDURE simple_sum(x, y) {
Expand All @@ -20,7 +25,7 @@ PROCEDURE loop_function(v) {
LOCAL i
i = 0
WHILE(i < 10) {
print("Hello World")
printf("Hello World")
i = i + 1
}
}
7 changes: 4 additions & 3 deletions test/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ target_link_libraries(
${NMODL_WRAPPER_LIBS})

if(NMODL_ENABLE_LLVM)
add_executable(testcodegen visitor/main.cpp codegen/llvm.cpp)
include_directories(${LLVM_INCLUDE_DIRS})
add_executable(testllvm visitor/main.cpp codegen/llvm.cpp)
target_link_libraries(
testcodegen
testllvm
visitor
symtab
lexer
Expand All @@ -106,7 +107,7 @@ if(NMODL_ENABLE_LLVM)
llvm_codegen
${NMODL_WRAPPER_LIBS}
${LLVM_LIBS_TO_LINK})
set(CODEGEN_TEST testcodegen)
set(CODEGEN_TEST testllvm)
endif()

# =============================================================================
Expand Down
Loading

0 comments on commit 6f66c71

Please sign in to comment.