Friday, June 20, 2008

Birthday season


Last Sunday we celebrated my baby daughter Helena's first birthday. She enjoyed it to be in the focal point of the party, but the guests had their fun too. There was another boy, around 16 months old and they played together, hugging each other now and then. Just cute.

My son turns 12 mid-July and he'll celebrate twice, at home and some days later in Brazil. This event will close the string of birthday dates in my family, commencing with mine on 29. of April and about 10 in between :-)
A kind of frenzy, especially when you are looking back. And people keep telling me that there used to happen a different kind of frenzy in respective years ten months before :-P

Thursday, June 12, 2008

OpenCL / CLang / CL

The most exciting thing that happens under sleep deprivation is a kind of stream of consciousness, where the brain goes into a purely associative mode. I am sleepy now and so I can embark on some journey which I tomorrow morning surely will dismiss as utter nonsense.

So, WWDC is progressing at the moment, some under-the-hood technologies are being introduced into the OS X system that is running on the Macs out there. Two of these sound interesting: "Grand Central Dispatcher" and OpenCL. There has been some speculation that utilizing GPU power (which is 99,9% idle, unless you are a framerate-wanking gamer) could be one of the directions of the future (besides multi-cores). Nvidia and ATI both offer technologies to do this at different levels, but they are incompatible. It is hard to imagine that Apple will provide developers several different and incompatible compilers and tell them to compile their compute intensive kernels with them, bundle them up in some big blob of application and ship it untested (how many developers will buy a bunch of video-cards for testing?).

Apple likes to provide seamless solutions. They have a proven concept in their OpenGL implementation, namely a machine-independent binary format that can be JITted to a wide variety of processors: LLVM. This would make Joe Developer's life pretty easy: compile your computing kernels to LLVM bitcode, and submit a quadruple to a runtime component (bitcode, characteristics, input data, continuation/consumer of results). The characteristics could state that it is integer- or FP-heavy code etc. After deciding which target will run the code, the LLVM JIT could distill the optimal machine code for the target and invoke it on the input data. When done, the continuation would be called, and would consume the results. Add some speculative execution (start the same job in several computing resources), a good deal of caching (no need to recompile bitcode again), heuristics and profile guided recompilation. This could be some yummy sauce.

RoughlyDrafted's blog so far provides the best intro to the new technology, and helped to start my association chain. What comes next is the fun part.

When hearing OpenCL the first time, I thought, what a sucky name. It reminds me of OpenGL, but it is not a graphics language. It reminds me of Common Lisp, but that is not an option. Open Computing Language. Hmmm, all programming languages compute stuff...

There is a little gem, still unpolished, called clang. The light footed successor of gcc in corporate environments. Compiling "curly brace" languages to LLVM! Wasn't OpenCL C-based? Those letters CL appear everywhere. What is the initials of the principal LLVM engineer, again?

Friday, May 16, 2008

Merged

It did not make it into LLVM release 2.3 (let's be conservative), but it looks like my use-diet branch will make it into LLVM 2.4! I have merged the relevant changes on last Saturday (2008-05-10), and so far it has not been reverted :-)

Owen did some memory statistics and found out that the SPEC benchmark 447.dealII had a 13% reduced memory footprint with my patches. This is about what I expected, and a pretty awesome result. Despite of the algorithmic overhead that has been (knowingly) added the compilation did not really get slower, indeed some nightly testers show slightly improved cpu times. Again, this is in line with my observed slight speedup on kimwitu++ benchmark's compilation time.

What's next?

My plans are to do some more API cleanup and then go after even more memory savings which I am already drafting in my fantasies... Whether they are feasible compile-time-wise is still an open question, but I am determined to find out.

Saturday, April 19, 2008

Absolutely, Positively Crazy

My previous two posts revolved around space optimizations in LLVM. The presented technique is a bit unconventional, and paired with the confusing nature of def-use chains, it can leave one quite puzzled...

Today in the LLVM IRC channel, chandlerc wondered whether I am eliminating the Value* from the Use struct. I corrected him, that, no, it is all about eliminating User*. But his question drove a nail im my head, making me think. After all, he was thinking of the def-use chain, and I was always dealing with user-use arrays. But deeply inside it is the same problem, a.k.a. the dual one:
  • for a user-use array the User* for all array members is constant, while the defs (Value*-s) vary,
  • for a def-use chain on the other hand the defs (Value*-s) are invariant, while the User*-s vary.
I wondered whether it is feasible to use the tagging technique to get rid of the Use::Val too? After all, it is just a linked list, and I have already demonstrated something similar in my previous posts.

It turns out that it is perfectly feasible, and the rest of this post demonstrates how...
...but please do not misunderstand, I do not propose this for LLVM (yet?).

Let's recapitulate what a def->use chain is:
There is an instruction that generates a value, thus defining it:

+-------+
Val = | Instr |
+-------+

Then there are other instructions which consume this value, but the above instruction only sees the Use objects chained up:

+-------+
| Instr |
+----+--+
| +---+ +---+ +---+
+---->|Use|--->|Use|--->|Use|---X
+---+ +---+ +---+
The X above means that Use::Next is filled with NULL. Otherwise Use::Next obviously contains the pointer to the next Use object.
So to apply waymarking we have to get away with two obstacles:
  1. There is no way to store something around the Use object (layout-wise), as we did with user-use arrays. We cannot go outside to place the Value*.
  2. Def-use chains can be expanded and shrunk dynamically.
It turns out that 1) is pretty easy, we do not store NULL in the last Use's Next field, but the Value*, with the ‹fullstop› waymark. So when we see ‹fullstop›, the Value* can be extracted from the upper bits.
So, let's assume the ‹stop› and ‹0›, ‹1› waymarks are also tucked on the Use::Next fields. Like usual, given any Use* we can pick up the digits, and at the ‹stop› we convert the digits to a Value* and we are done. This is nice and dandy, until we remember 2). It appears a bit more tricky. Obviously the insertion/deletion will remove waymarks or add new ones. Our algorithm may begin producing bogus Value*-s. So is there any solution to this problem?
I believe there is. I have no proof but I conjecture that each destructive operation should simply place a ‹stop›, and when the algorithm is changed to only accept 32 digits between two ‹stop›s, then we are on the safe side. The corruption gets detected, an uncorrupted segment can still be found, downstream. The corruption can be repaired later (e.g. when traversing the def-use chain).

Well this is not a very efficient way of doing things, so it probably only pays off when space is extremely limited and compilation/optimization speed is not paramount. But in these applications an 8-byte (down from 16/12 bytes) Use struct is definitely tempting.

Monday, April 14, 2008

Use-diet Update

As of r49698 the use-diet branch finishes the testsuite (about 2000 tests) without regressions. Actually the User* member of the structure is not eliminated yet, but instead deliberately set to NULL in various places, and if present, I assert that it equals the computed value.

It was quite a fight to get thus far, but it is done! Time to open a good bottle of wine...

The next days will be devoted to cleaning up the sometimes messy details, doing some statistics and cautiously trying to merge back my work to trunk.

Wish me luck.

Sunday, April 13, 2008

LLVM Data Structures, and Putting Use on Diet

There is a good deal of refactoring going on in the LLVM universe, to wit Dominic's renaming of LLVM*Builder to IRBuilder with assorted simplifications...
The train is moving fast, getting on board is harder, but rewarding. LLVM does not commit to API (or less even, binary) compatibility, so meaningful changes get in swiftly and without much bureaucracy. A simple mail, warning people of the change in advance, and when accomplished, instructions how to do the conversion, are enough to make folks happy.

There are reforms going more than skin deep, too. Clang is is getting a datastructure rewrite with nice gains, and myself is about to reduce the size of the Use struct by 4 bytes, from 16 to 12 (on a 32bit system). The 25% savings is nothing to sneeze at, considering that this struct is the most frequently allocated one in LLVM. And now that I have the functionality basically implemented on a branch, I can say, the idea is working! Consequently, I have the courage to blog about it :-)

So what is this Use-diet all about? The Use struct is the home of the pointers to all Values that an Instruction references. But each Value has to track all of its Users (i.e. the Instructions), and Use provides forward and backward pointers to chain those up. These are the essential 3 pointers. But why is Use 16 bytes? Because in some situations it is important to get back to the User of the referred Value. So it is 4 pointers in total.

Seemingly this is how it works™ and there is nothing that can be shaved off. But wait! Don't all Uses belonging to an Instruction come lined up in a contiguous array? What if we could mark the last Use specially and put a pointer to the Instruction behind it? Or even allocate the Uses immediately in front of Instruction (memory-layout wise)?

These were the first ideas how my brain-storming with Sabre began. After several exchanged emails, we settled on a concept that is IMHO really beautiful. We use 2 bits (the least significant ones, which are always zero, normally) of one of the pointers in Use to implement a serial line-like protocol of waymarks that guides us to the end of the array in some reasonably few steps. I will not detail the algorithm, since it is documented elsewhere, I will only say that there are four kinds of waymarks: ‹fullstop›, ‹stop›, ‹0› and ‹1›.
  • Fullstop means we are at the end already,
  • Stop means begin gathering digits, or if already done so, convert them to an offset, that brings us to the end,
  • 0 and 1 are the binary digits to be picked up.
It is clear to see that for small arrays there are only a small number of operations needed to determine the end of the array. The complexity of the algorithm is O(log N). So we do not really have to get concerned with, say, 10000 predecessors to a PHI node :-). The description even contains a Haskell snippet to encode and decode such offset information.
For reference I shall present it here:
> import Test.QuickCheck
>
> digits :: Int -> [Char] -> [Char]
> digits 0 acc = '0' : acc
> digits 1 acc = '1' : acc
> digits n acc = digits (n `div` 2) $ digits (n `mod` 2) acc
>
> dist :: Int -> [Char] -> [Char]
> dist 0 [] = ['S']
> dist 0 acc = acc
> dist 1 acc = let r = dist 0 acc in 's' : digits (length r) r
> dist n acc = dist (n - 1) $ dist 1 acc
>
> takeLast n ss = reverse $ take n $ reverse ss
>
> test = takeLast 40 $ dist 20 []
>

Printing gives: "1s100000s11010s10100s1111s1010s110s11s1S"

The reverse algorithm computes the
length of the string just by examining
a certain prefix:

> pref :: [Char] -> Int
> pref "S" = 1
> pref ('s':'1':rest) = decode 2 1 rest
> pref (_:rest) = 1 + pref rest
>
> decode walk acc ('0':rest) = decode (walk + 1) (acc * 2) rest
> decode walk acc ('1':rest) = decode (walk + 1) (acc * 2 + 1) rest
> decode walk acc _ = walk + acc
>

Now, as expected, printing gives 40.

We can quickCheck this with following property:

> testcase = dist 2000 []
> testcaseLength = length testcase
>
> identityProp n = n > 0 && n <= testcaseLength ==> length arr == pref arr
> where arr = takeLast n testcase

As expected gives:

*Main> quickCheck identityProp
OK, passed 100 tests.
Btw., QuickCheck is awesome!

So where are the uses of this algorithm outside of LLVM?
There is not much of thinking needed to generalize the array to other data structures, which may permit mutating of the node contents, but disallow insertions. Linked lists are an example since one can only cons up stuff to the head. Doubly-ended arrays with fast size() operation (given a pointer to one element) can be implemented if there is another pointer in each node that we can use for storing waymarks to the start of the array. Deques also could work like this, but they too, need to be fully built up before putting in the waymarks.

This all reminds me of cons-hashing but is really a more powerful concept. Let's call it waymarking! And then let the garbage collector put in the waymarks for us...
Down with O(n) complexity on linked-list's length operation!

Monday, March 31, 2008

ETAPS Budapest

I am in Budapest since Saturday, and I love it. The weather is excellent, I am in the middle of some very nice people and I can do what I like most: brainstorming cool concepts.
At a satellite event yesterday (DCC - Designing Correct Circuits) I met Tim and after the workshop we actually came to chat a plenty on Omega. I very much felt like a satellite, because I did not participate at the conference itself, just hanging around the lobby and the workshop rooms, using the "free" WLAN and thinking about what to do next.
Today then was our first Omega hacking day. Building on the googlecode repository's trunk version Tim checked in everything he had on his laptop, and now we can do concurrent development. Both the documentation and the release management toolchain have been overhauled and checked in, along with a testsuite.

This is already much more than what I dreamed of...

Tomorrow we'll go thru the issue list, squashing as many bugs as possible and I might get back to the LLVM generation branch. Another interested student will join us, an Hungarian guy called Tamás.

This is finally the place to thank Dr. Horváth Zoltán, who has very kindly offered us rooms in the university to have our hacking sessions in complete peace.