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

[GSoC] LLVM IR dumping #6135

Merged
merged 20 commits into from
May 9, 2017
Merged

[GSoC] LLVM IR dumping #6135

merged 20 commits into from
May 9, 2017

Conversation

coodie
Copy link
Contributor

@coodie coodie commented Apr 28, 2017

This PR adds convenience functions for dumping LLVM IR of given chapel function.

Need for such functions occurred when I started to work on adding LLVM vectorization tests for chapel test suite. They might also be useful in further work on LLVM.

Even very tiny chapel source files generate very big LLVM IR, when working with LLVM we usually need to look only at a piece of code. It seems reasonable to abstract our code into a function and then dump IR of function. There is another problem of chapel internal types, because they are in global space and thus cannot be enclosed in function, but I see it as another problem.

Compiler runs many transformations on LLVM IR, and sometimes we'd like to know if some optimizations occured or not (for example, was this loop vectorized?). At this moment there are 3 possibly useful places where we'd like to dump part of our code:

  1. Non-optimized - before any kind of optimization of function occurs. Just raw, compiler generated IR.
  2. Basic optimization - there are some basic optimizations run on a function after it is generated.
  3. Big optimization - After all big, whole modular, complicated optimization passes are run.

There might be a possibility and a need to add more granularity to 3rd point, like after which pass should we exactly dump a function.

@coodie coodie changed the title Llvm fdump LLVM IR dumping Apr 28, 2017
@mppf
Copy link
Member

mppf commented Apr 28, 2017

I think it would be better, in this use case, if you could print to stdout. Also the LLVM docs say ->dump is for debugging. Is there a 'print' method of some sort you could use instead?

char llvmFuncDumpName[FUNC_NAME_MAX+1] = "";
int llvmFuncOptDump = 0;

void llvmFunctionDump(int optLevel, const std::string &name) {
Copy link
Member

Choose a reason for hiding this comment

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

I think this function has the wrong arguments. I think it should accept a Chapel Function* and an llvm::Function*.
Then, compare llvmFuncDumpName against chapelFunction->name and chapelFunction->cname.

@@ -43,7 +43,6 @@

// LLVM debugging support
#include "llvmDebug.h"

Copy link
Member

Choose a reason for hiding this comment

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

Let's put this space back, since it doesn't have to do with your intended change set.

@@ -75,6 +102,7 @@ void Symbol::codegenDef() {

void Symbol::codegenPrototype() { }


Copy link
Member

Choose a reason for hiding this comment

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

Let's take this space away.

@@ -1261,6 +1289,9 @@ void FnSymbol::codegenDef() {
body->codegen();
flushStatements();

#ifdef HAVE_LLVM
#endif

Copy link
Member

Choose a reason for hiding this comment

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

Take out this ifdef doing nothing?


#ifdef HAVE_LLVM
void llvmFunctionDump(int optLevel, llvm::Function *llvmFunc, FnSymbol *chapelFunc) {
static llvm::Function *func = NULL; //Store function once we've found it
Copy link
Member

Choose a reason for hiding this comment

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

This is really so that you can make the call in makeBinaryLLVM work nicely, right? Maybe put a comment to that effect?

extern int llvmFuncOptDump;

#ifdef HAVE_LLVM
void llvmFunctionDump(int optLevel, llvm::Function *llvmFunc = NULL, FnSymbol *chapelFunc = NULL);
Copy link
Member

Choose a reason for hiding this comment

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

Let's put a comment here saying what this function does. Something like

Prints out LLVM IR for the function selected by llvmFuncDumpName at the optimization level llvmFuncOptDump. If llvmFunc and chapelFunc are NULL, this function assumes it has been called previously with non-NULL arguments for these functions.

But I find this behavior a little bit odd and wonder if it would be better to just make llvmFunctionDump's static variable in to a global. Is that what you had before? Sorry if I'm leading you in a circle..

@@ -704,6 +704,8 @@ static ArgumentDescription arg_desc[] = {
{"", ' ', NULL, "LLVM Code Generation Options", NULL, NULL, NULL, NULL},
{"llvm", ' ', NULL, "[Don't] use the LLVM code generator", "N", &llvmCodegen, "CHPL_LLVM_CODEGEN", NULL},
{"llvm-wide-opt", ' ', NULL, "Enable [disable] LLVM wide pointer optimizations", "N", &fLLVMWideOpt, "CHPL_LLVM_WIDE_OPTS", NULL},
{"llvm-fdump", ' ', "<name>", "Dump LLVM Intermediate Representation of given function to stdout", "S256", llvmFuncDumpName, "CHPL_LLVM_FDUMP", NULL},
{"llvm-fdump-opt", ' ', "<opt>", "Specifies from which LLVM transformation phase to print function: 0 - before any transformation, 1 - basic cleaning, 2 - full optimization", "I", &llvmFuncOptDump, "CHPL_LLVM_OPT_FDUMP", NULL},
Copy link
Member

Choose a reason for hiding this comment

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

I think it might be more reasonable for llvm-fdump-opt to take in a string, so that if we add/change optimization levels we don't have to renumber & change tests. Also I might prefer different flag names.

I might like
--print-llvm-ir instead of llvm-fdump and I'm not sure what to call llvm-fdump-opt. Maybe print-llvm-ir-after and then it can take start simplify and optimize, for example?

(I think we'll want to ask some others about that part so make sure to put the user interface for this feature in your PR message. Generally the user interface will receive more scrutiny than the change itself).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this needs attention of other developers, I'm not going to focus on that part. I'll just write tests and change them later accordingly.

Copy link
Member

Choose a reason for hiding this comment

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

Right, but could you summarize the usage of the new feature in an email to chapel-developers and/or in the PR message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. I'll write all down in PR message and then send e-mail pointing to this PR. I'm not sure which is preferred one (e-mail or PR), but I suspect most of devs don't look that often into PR section. Whenever I'm doing these tasks I make quiet assumption that you inform the rest of developers.

@@ -1867,6 +1867,7 @@ void makeBinaryLLVM(void) {
output.keep();
output.os().flush();


Copy link
Member

Choose a reason for hiding this comment

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

Let's remove this new space.

@coodie
Copy link
Contributor Author

coodie commented May 2, 2017

I've run into small Issues with figuring out simple IR testcase where non-optimized version would differ from simplify optimization. As far as I understood code there is some dataLayout pass which is architecture dependent, and -O2 pass on a function (yes! the one from clang), more details in prepareCodegenLLVM in clangUtil.cpp. For some reason these 'basic' optimizations don't occur. This loop however:

proc test()
{
  forall i in 1..100
  {
    writeln(i);
  }
}

Shows some differences, but they are very minor and generated IR takes ~400 lines of unreadable and difficult to understand code, so I decided not to use it for testing this feature.

@@ -1204,6 +1222,9 @@ void FnSymbol::codegenDef() {
#ifdef HAVE_LLVM
func = getFunctionLLVM(cname);

if(strcmp(llvmFuncDumpName, name) == 0)
llvmFuncDumpCName = strdup(cname);
Copy link
Member

Choose a reason for hiding this comment

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

Because of the way the Chapel compiler manages strings, you don't need to call strdup here. Just make it point to cname.

@@ -1281,11 +1302,17 @@ void FnSymbol::codegenDef() {
INT_FATAL("LLVM function verification failed");
}
}

if(llvmFuncOptDump == 0 && strcmp(llvmFuncDumpName, name) == 0)
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be nice if 0 meant no dumping, to save some strcmparing in the common case

@mppf
Copy link
Member

mppf commented May 2, 2017

Thanks for the improvements! What we need to do next:

  • get feedback on the new compile flags. I'll send an email about that.
  • simplify your tests even more

About simplifying the tests even more, in this case I'm not too worried if the output differs for different optimization levels. Let's test it with as trivial functions as are possible.

@hzhang86
Copy link

hzhang86 commented May 2, 2017

I prefer the flags:

--print-llvm-ir --print-llvm-ir-after

where is:
* none -- before any optimization
* basic -- after basic simplification
* full -- after full optimization

That's just my own favour.

@bradcray
Copy link
Member

bradcray commented May 3, 2017

Personally, I'd probably choose the names --llvm-print-* rather than --print-llvm-* just to put the llvm-specific aspect front-and-center, but I don't feel strongly about this if you disagree. I also agree that the mnemonic flag settings rather than integers seem more attractive.

@mppf
Copy link
Member

mppf commented May 3, 2017

OK, so if we combine all of the thoughts, we end up with:

 --llvm-print-ir <function-name> --llvm-print-ir-after <opt-name>

where <opt-name> is:
    * none -- before any optimization
    * basic -- after basic simplification
    * full -- after full optimization

@coodie
Copy link
Contributor Author

coodie commented May 5, 2017

Well I the only thing that comes to my mind would be change --llvm-print-ir-after to --llvm-print-ir-stage, but that's not a big deal.

And when it comes to testing I've came up with simple idea, which would make tests a bit more reasonable. When we dump LLVM IR representation I guess it would be good to add comment in printed LLVM IR saying from which optimization stage the printed IR comes from. Like:

$ chpl --llvm --llvm-print-ir mainTest --llvm-print-ir-after basic test.chpl

; Dump of function mainTest after basic optimizations
define internal void @mainTest_chpl(i64 %n_chpl) {
entry:
  %chpl_macro_tmp_713 = alloca i64
  store i64 %n_chpl, i64* %chpl_macro_tmp_713, !tbaa !0
  %_ic__F1_high_chpl = alloca i64
  %i_chpl = alloca i64
  br label %mainTest_chpl_2blk_body_
...

Then in tests we can check if we printed that line. It doesn't of course ensure that printed IR is actually from stage, but provides extra info to the user and can be good starting point for correctness tests.

@mppf
Copy link
Member

mppf commented May 5, 2017

I like the strategy to improve the tests. Re the naming, it would be fine with me to have --llvm-print-ir-stage. I think you have enough information to adjust this PR.

@@ -704,8 +709,8 @@ static ArgumentDescription arg_desc[] = {
{"", ' ', NULL, "LLVM Code Generation Options", NULL, NULL, NULL, NULL},
{"llvm", ' ', NULL, "[Don't] use the LLVM code generator", "N", &llvmCodegen, "CHPL_LLVM_CODEGEN", NULL},
{"llvm-wide-opt", ' ', NULL, "Enable [disable] LLVM wide pointer optimizations", "N", &fLLVMWideOpt, "CHPL_LLVM_WIDE_OPTS", NULL},
{"llvm-fdump", ' ', "<name>", "Dump LLVM Intermediate Representation of given function to stdout", "S256", llvmFuncDumpName, "CHPL_LLVM_FDUMP", NULL},
{"llvm-fdump-opt", ' ', "<opt>", "Specifies from which LLVM transformation phase to print function: 0 - before any transformation, 1 - basic cleaning, 2 - full optimization", "I", &llvmFuncOptDump, "CHPL_LLVM_OPT_FDUMP", NULL},
{"llvm-print-ir", ' ', "<name>", "Dump LLVM Intermediate Representation of given function to stdout", "S256", llvmPrintIrName, "CHPL_LLVM_FDUMP", NULL},
Copy link
Member

Choose a reason for hiding this comment

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

update enviro var names too e.g. "CHPL_LLVM_FDUMP"

@@ -1222,8 +1238,8 @@ void FnSymbol::codegenDef() {
#ifdef HAVE_LLVM
func = getFunctionLLVM(cname);

if(strcmp(llvmFuncDumpName, name) == 0)
llvmFuncDumpCName = strdup(cname);
if(strcmp(llvmPrintIrName, name) == 0)
Copy link
Member

Choose a reason for hiding this comment

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

can this be if (llvmPrintIrNameStageNum !=0 && strcmp(...) == 0 ) ?

{LLVM_FULL_STAGE_NAME, LLVM_FULL_STAGE_NUM}
};

std::map<int, std::string> llvmStageRevMap =
Copy link
Member

Choose a reason for hiding this comment

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

std::map seems overkill for these 3-element debugging lists. I'd personally probably just use an array for int -> string and then linear search for the other way... but there's not anything wrong exactly with using std::map.

Copy link
Contributor Author

@coodie coodie May 5, 2017

Choose a reason for hiding this comment

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

I have to agree with you that map is a bit of overkill.

In this case it was just easier for me to assign (value)->(key) using std::map notation. Normally I'd like to have std::array or std::vector equivalent looking like this:

std::vector llvmStageRevMap =
{
    [LLVM_NONE_STAGE_NUM] = LLVM_NONE_STAGE_NAME,
    [LLVM_BASIC_STAGE_NUM] = LLVM_BASIC_STAGE_NAME,
    [LLVM_FULL_STAGE_NUM] = LLVM_FULL_STAGE_NAME
};

But C++ doesn't support such assignment to vector.

I consider to even remove map, and simply use bunch of 'if, else' in places where I actually use this: llvmFunctionDump, and verifyStageAndsetStageNum, but this is just my programming style to use map for this kind of task.

Copy link
Member

Choose a reason for hiding this comment

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

It can just be

char* llvmStageRevMap[] = {
  "",
  LLVM_NONE_STAGE_NAME,
  LLVM_BASIC_STAGE_NAME,
LLVM_FULL_STAGE_NAME }

Anybody adding stages will need to modify it anyway

@@ -0,0 +1,5 @@
proc mainTest()
{
writeln("Hello World!");
Copy link
Member

Choose a reason for hiding this comment

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

Can it just be an empty proc?

mainTest_chpl_2blk_body_: ; preds = %entry
%0 = load %string, %string* @_str_literal_1924, !tbaa !0
store %string %0, %string* %local__str_literal_1924_chpl, !tbaa !0
call void @writeln_chpl2(%string* %local__str_literal_1924_chpl, i64 3, i32 51)
Copy link
Member

Choose a reason for hiding this comment

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

writeln_chpl2 won't be stable as the compiler changes.
Can you put a PREDIFF that just greps out everything except for the "LLVM IR representation" comment?

@@ -1204,6 +1245,9 @@ void FnSymbol::codegenDef() {
#ifdef HAVE_LLVM
func = getFunctionLLVM(cname);

if(strcmp(llvmPrintIrName, name) == 0)
Copy link
Member

@mppf mppf May 8, 2017

Choose a reason for hiding this comment

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

Just to be totally clear & to minimize the function call, can this be

if (llvmPrintIrStageNum != llvmStageNum::NOPRINT && strcmp(llvmPrintIrName, name) == 0)

?

extern const char *llvmStageName[llvmStageNum::LAST];

const char *stageNameFromStageNum(int stageNum);
int stageNumFromStageName(const char* stageName);
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't these functions be prefixed with 'llvm' e.g. 'llvmStageNameFromStageNum' ?

static void verifyStageAndSetStageNum(const ArgumentDescription* desc, const char* arg_unused)
{
int stageNum = stageNumFromStageName(llvmPrintIrStage);
if(!stageNum)
Copy link
Member

Choose a reason for hiding this comment

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

Everywhere else, stageNum is only ever one of the enum values. So shouldn't this say

if (stageNum == llvmStageNum::NOPRINT)

?

extern int llvmPrintIrStageNum;

namespace llvmStageNum {
enum { NOPRINT = 0,
Copy link
Member

Choose a reason for hiding this comment

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

I'd consider making this a typedef enum and then using that type instead of int for llvmPrintIrStageNum.

man/chpl.rst Outdated
**--llvm-print-ir-stage <stage>**
Picks stage from which to print LLVM IR of function defined in
**--llvm-print-ir**.
Chapel compiler runs many different optimization passes each of which
Copy link
Member

Choose a reason for hiding this comment

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

Add a The -> "The chapel compiler runs..."

man/chpl.rst Outdated
Picks stage from which to print LLVM IR of function defined in
**--llvm-print-ir**.
Chapel compiler runs many different optimization passes each of which
can change IR of function. This option allows to pick IR of function
Copy link
Member

Choose a reason for hiding this comment

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

"can change IR of function" -> "can change the IR of functions"

"allows to pick IR" -> "allows one to pick the IR"

@mppf
Copy link
Member

mppf commented May 8, 2017

I ran full local testing and got these failures:

[Error matching compiler output for compflags/bradc/help/userhelp]
[Error matching compiler output for compflags/bradc/printstuff/zall]
[Error matching compiler output for compflags/bradc/printstuff/zcopyhelp]
[Error matching compiler output for compflags/bradc/printstuff/zhelplice]
[Error matching compiler output for compflags/bradc/printstuff/zhelpvers]

@mppf
Copy link
Member

mppf commented May 9, 2017

Passed full local testing. I needed to make some space changes to chpl.rst in order to get make docs to work. I plan to merge and then commit the space changes.

@mppf mppf merged commit 8d704f0 into chapel-lang:master May 9, 2017
mppf added a commit to mppf/chapel that referenced this pull request May 9, 2017
mppf added a commit that referenced this pull request May 9, 2017
Adjust spacing in chpl.rst to be valid rst

Follow-on to PR #6135 so that `make docs` completes without error.
@coodie coodie changed the title LLVM IR dumping [GSoC] LLVM IR dumping Aug 28, 2017
@@ -704,6 +713,8 @@ static ArgumentDescription arg_desc[] = {
{"", ' ', NULL, "LLVM Code Generation Options", NULL, NULL, NULL, NULL},
{"llvm", ' ', NULL, "[Don't] use the LLVM code generator", "N", &llvmCodegen, "CHPL_LLVM_CODEGEN", NULL},
{"llvm-wide-opt", ' ', NULL, "Enable [disable] LLVM wide pointer optimizations", "N", &fLLVMWideOpt, "CHPL_LLVM_WIDE_OPTS", NULL},
{"llvm-print-ir", ' ', "<name>", "Dump LLVM Intermediate Representation of given function to stdout", "S256", llvmPrintIrName, "CHPL_LLVM_PRINT_IR", NULL},
{"llvm-print-ir-stage", ' ', "<stage>", "Specifies from which LLVM optimization stage to print function: none, basic, full", "S256", llvmPrintIrStage, "CHPL_LLVM_PRINT_IR_STAGE", &verifyStageAndSetStageNum},
Copy link
Member

Choose a reason for hiding this comment

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

@coodie and @mppf: In putting together the release CHANGES file, I only noticed these flags for the first time. These strike me as developer, rather than user, flags. Is there an argument for keeping them in user space rather than pushing them down into developer space? (the arguments for pushing them down being to keep the --help output and man page slightly shorter). Thanks.

Copy link
Member

Choose a reason for hiding this comment

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

The argument for having them in user space is "Users might want to see the LLVM IR output" but I think it'd be reasonable to call such users "developers", whatever that means. I'll make a PR to move it down.

mppf added a commit that referenced this pull request Sep 21, 2017
Make --llvm-print-ir a developer flag

Suggested by @bradcray.

PR #6135 added --llvm-print-ir and documented it as a user feature... But this feature is really only useful to developers or expert users who might used developer flags anyway.

Passed full local testing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants