Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial LLVM codegen vistor routines #457

Merged
merged 19 commits into from
Dec 22, 2020
Merged
Show file tree
Hide file tree
Changes from 18 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
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})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI : I changed this because this was a bug - we were finding LLVM only for Clang compiler.

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
159 changes: 134 additions & 25 deletions src/codegen/llvm/codegen_llvm_visitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,154 @@

#include "llvm/IR/BasicBlock.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"
#include <llvm/IR/Constants.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();
pramodk marked this conversation as resolved.
Show resolved Hide resolved
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;
pramodk marked this conversation as resolved.
Show resolved Hide resolved
}

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();
pramodk marked this conversation as resolved.
Show resolved Hide resolved
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick : indentation

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, what is the indentation used for the project? I noticed that headers use 2 spaces, and implementation uses 4 spaces. So I will just use that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I was mistaken here. I see that line 103 is indented for loop!

For indentation, we use 4 spaces everywhere. In fact, assuming you have clang-format 8/9/10, and cmake option -DNMODL_CLANG_FORMAT=ON, doing make clang-format should format everything consistently!

If you see 2 spaces somewhere, let me know.


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();
// Clear named values for now, since there is no support of global variables yet.
// It will be better to keep a separate global map (or use module?).
pramodk marked this conversation as resolved.
Show resolved Hide resolved
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);
pramodk marked this conversation as resolved.
Show resolved Hide resolved

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