December 25, 2004

.NET disassembler

I made a lot of progress on my C# to C++ translator today. I have written a simple disassembler that loads the EXE and PDB for a .NET assembly and outputs all the interesting metadata, debug information (C# source line numbers and variable names) and IL assembly using the new .NET 2.0 reflection API. It was surprisingly easy, only 300 lines of code. All I need to do now is modify it to output C++ code instead of IL disassembly. I did have some problems disassembling C# code that uses generics. I suspect the new reflection API may not be quite finished yet, it is only a beta release after all.

I have been thinking further about where C# might fit into games development. We are already using it for tools, I am considering whether it is useful for actual game code. Apart from the need to write a translator in order to use C# on a console, I think my next biggest concern is the .NET garbage collector. Although garbage collection is one of the features of the .NET framework that makes it so easy to use, I don't think consoles are sufficiently powerful to use it yet. It isn't just the number of cycles it burns in order to free memory. Another problem is that it doesn't spread the CPU cost evenly, which could make the frame rate stutter.

I think the simplest solution is simply to abandon garbage collection and use something simpler like reference counting, combined with manual memory management where necessary. This will probably also mean abandoning a large portion of the .NET class library.

Fortunately the C# language is sufficiently independent from the rest of the .NET framework that this will not be a problem. I will probably look into taking the C# source code for the Mono implementation of the .NET class library and just pick out the useful bits that do not rely on garbage collection.

A garbage collector of some sort might be useful for diagnostics of course. So long as the game does not need to run it in release builds it might be a useful way of finding memory leaks.

To anyone concerned that I spent Christmas day messing around programming, be assured that I did do Christmas things. I had a very tasty prime rib with some of my friends from work. Then I went to the beach and did some reading. This is the first Christmas where I haven't had to wear a big winter coat. I love California!



Comments:
Hey Al,

So, in your opinion, if we remove garbage collection from C#, what are the main advantages it has over C++ for game development? And do you think those advantages outweight the dissadvantages of the extra complexity introduced?
Happy holidays!

--Noel
 
Oh boy that's a hard question. I'll have to think about it a bit. I was going to make post along those lines but I haven't quite got my thoughts in order yet. Maybe after lunch :)
 
As I see things, the key advantage of C#, and many other languages such as Java, Python or Lisp, for games development is their ability to express the meaning information and information flow as a first class language feature. This is something that is absent from C++. I have seen many attempts to emulate it in C++ using crazy macro and template meta-programming techniques but nothing as clean as the higher-level features available in some other languages. In fact, I have implemented such a scheme in C++ as I believe you have for your book.

What do I mean exactly? In C# it comes in three parts. First of all, C# has a reflection API, which can be used in two ways. A C# program is able to use the reflection API to examine its own structure at runtime. I already covered the applications of reflection in games code and some other benefits of higher-level language features in this post:

http://alpatrick.blogspot.com/2004/12/introspection-kicks-ass.html

In summary, reflection can be used to automate many information related processes such as serialization. Reflection can be used in a second way, which is appropriate when the first way would not meet performance requirements. The reflection API can be used by a compiler plugin to examine the structure of a compiled assembly and automatically generate more code that can then be linked with the original assembly. The generated code has the potential to carry out the information process faster than one based on runtime reflection queries.

The second part is the object model. The C# object model is unambiguous and 100% consistent with the reflection API. Compare this with C++. In C++, does a pointer mean pointer to object or pointer to array? If it is an array, what size is the array? If a pointer once referenced an object that has since been freed, how can a program know that it is unsafe to dereference? Given a union, which members are currently valid? How should an object be instantiated? Maybe it is safe to use its new operator. Perhaps it should only be instantiated through a factory? Maybe it is global or allocated on the stack? At runtime, how does one determine the runtime class of an object? Given a function that takes a reference type as a parameter, does this mean the function makes a copy of the reference or does it mean that it returns something by modifying the referenced value? Given an integer variable, does this represent a true integer or a set of flags? If flags, which enumeration are the flag values taken from?

The third part is an extension of reflection where the programmer can associate custom metadata annotations with the various elements of the program: assembles, classes, members, parameters, etc. This is useful when the reflected structure is not sufficient to imply the true meaning of the program in terms of information and information flow. In C++, given a member function called GetPosition(), does that imply a property that should be serialized or merely a utility function that duplicates some of the serialized state covered by the GetLocalToWorld() member function? In C# the same problem would be present, except one can simply apply an annotation to direct the serializer what (not) to serialize.

In a nutshell, C++ focuses on letting the programmer express what a program should do in imperative terms. The primary audience for the program is other programmers and the CPU. In addition to this, C# focuses on helping the programmer express what a program means in a broader context. The audience is extended to include tools, databases, file formats, network protocols, etc.

C# is like the combination of an imperative object oriented language and a schema language. C# has many other benefits to offer but I think the potential of reflection dwarves the others.

Your second question was do the advantages of C# outweigh the disadvantages of the extra complexity involved? I'm not sure exactly what you mean by "extra complexity". I am going to guess you mean the effort in developing and maintaining the C# to C++ translator and the additional friction caused by another layer of language between the programmer and the CPU?

I do not currently have enough information to answer this question. That is one of the reasons I am writing this prototype.

With regard to the friction caused by another language layer, I will say that I have already demonstrated that other languages can be tightly integrated with C++ development environment. A C++ debugger can easily be used for source level debugging of a C# program. The C# to C++ and C++ to native code build steps can easily be integrated into any good build system. The Visual Studio allows C# and C++ code to be edited in a single IDE. And Visual Studio 2005 will support automatic refactoring :)

Let's turn this on its head. Considering that there are alternatives, does C++ not cause considerable friction through its inability to express the meaning of the information it pocesses?

Another potential pitfall would be build times. We already see appalling build times for C++ projects. Will adding C# -> MSIL and MSIL->C++ not make the build times even worse? I think it has the potential to make them better, although I have not yet verified this. The reason is that the C#->C++ translator can automatically apply techniques, like the pimpl pattern that speed compilation times. It can also ensure that include directives are minimized and that forward declarations are used wherever possible. So the C++->native code part of the process should be considerably faster. C#->MSIL is already very fast. As an experiment I just compiled the SharpDevelop open source IDE from scratch in 38 seconds using NAnt. That's a total of 8MB of source code. A dependency check with no changes took 3 seconds. So the C#->MSIL part is insignificant compared to the C++->native part. We'll just have to see how fast the MSIL->C++ part is!

I am by no means finished on this subject. Watch for future posts :)

Al
 
I believe that it's reflection what makes GC possible. Garbage collector in kaffe or in mono does't give precise field-by-field memory management, he looks through all memory - on word-by-word basis.

But walking only by some fields isn't enough for me. I wish to have the way to clear entire graph of objects without traicing their fields, because (in my situation) I know that there is a lot of small linked objects and they are not linked to other objects in the heap.

In C++ I can implement this with custom memory allocation and deallocation, but in C# I can't.

Might be it is possible with AppDomain unloading?
But in any case I have no way to prevent losing CPU power for GC in that domains.

So, I am interesting in using C# to C++ compiler too. The main reason is to get control over memory management.
 
Precise garbage collection requires that the garbage collector is able to traverse a program's runtime reference graph. Reflection is one (but not the only) way of allowing that. It will certainly be easier to support precise GC in a language that provides reflection.

You describe conservative GC. This is an approximation of precise GC where every word of allocated memory in the heap is assumed to be a field referencing another object. This makes GC possible in languages where the GC has no way of identifying fields within objects, like most assembly languages, C and C++. A serious problem is that the GC can see false positives, that is it will fail to free an unreachable object because it seems to be reachable under the false assumptions it is forced to make.

These problems are indeed one of the reasons I am investigating a C#->C++ translator. Memory management is a serious concern in my problem domain (console video games). I am currently considering using reference counting as an alternative to GC. See my recent post. http://alpatrick.blogspot.com/2005/01/automatically-detect-all-memory-leaks.html.

Can you tell me what your problem domain is?

I don't believe AppDomains are going to help. They are more a kind of light weight process. I see how you might use them to force destruction of objects but I don't think it is going to faster than GC!

In garbage collected C# you can always use arrays of structs to implement simple memory pools or free lists? Or alternatively, use a combination of C# and C++/CLI, where you write as much code as possible in C# but where performance matters, use C++/CLI?
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?