After a lot of work on the metadata subsystem, I decided to release a new version of Campy. This release fixes a lot of issues with programs that use/reference Net Core and Net Standard, impacting the closure of kernels. The memory allocation subsystem was also improved, although it is still just a first-fit free block allocator. There are some corrections for various CIL instructions, like ldlen, ldnull, and newobj. Generics still do not work. After some thought, rewriting a generic instance like “List<int>” into a non-generic Mono.Cecil.TypeDefinition where the name is “List<int>”, and every damn CIL instruction that references a generic argument is rewritten, isn’t going to work when System.Reflection is added. FFT finally works again, although I did find out that System.Numerics.dll in Net Core (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.7\System.Numerics.dll) does not contain CIL, which you can verify yourself using DotPeek. It looks like–as with netstandard.dll–that System.Numerics.dll forwards types to System.Runtime.Numerics.dll–which does contain the CIL for things like “Complex operator +(Complex, Complex)”! Unfortunately, I found out just as I was about to release Campy that DNA does not handle any x64 Net Core assemblies on Ubuntu, while still working on Windows. It turns out that DNA does not read 0x8664 machine PE files, which is produced on Ubuntu. So, many last minute changes to get the Ubuntu platform working. It all means that there is still a lot to change in DNA to bring it up to snuff with respect to Mono, Net Core, Net Standard, Net Framework.
After using the DNA code for a while, I’ve identified some of the problems with the implementation that need to be corrected. Other problems were noted in Matt Warren’s article, and in the original DNA Git repository. Several problems mentioned have already been fixed.
- DNA does not conform to ECMA 335. There are missing table types, described below. The problem is that if any PE/assembly is read that contains one of these missing table types, DNA will not work, and likely you won’t even know! For example, in the original code, when reading a table that followed the missing table input, I recall it would segv because null would be passed to strlen. The following table illustrates the current state of DNA.
|Table number (base 10)||Type name||In ECMA 335 6th Ed. June ‘12||In CodeProject 12585||In original DNA||In Blazor DNA||In GPU DNA so far|
- The parser for signatures is just terrible. The parser should be an LL-like parser, which it sort of does on first glance seems to resemble, but actually isn’t. For example, MetaData_DecodeSigEntry() is used to decode the signature entry field. But, it is also called in many other places to just get a 32-bit unsigned integer. IT SHOULD NOT! That’s not how parsers should ever be written! It should follow the syntax descriptions of the ECMA 335 spec, section II.23.2, and from that, using the Dragon Book, a nice implementation written. This code needs to be completely rewritten.
- There is no tool for a human readable print out of the PE file metadata tables for debugging. I have added “CampyPeek” to fix this problem.
- Old Blazor code changed MetaData_DecodeSigEntry() in metadata.c, but it isn’t clear why. I will need to chase this down.
- Assembly resolution in DNA is a problem for the GPU. In DNA, assembly “resolution” is sort of done with function CLIFile_Load() in CLIFile.c. “Probing” occurs here, just opening the file in the current directory. Unfortunately, probing can only work if the files are pre-loaded into the GPU file system. So, assembly resolution doesn’t following that in the standard sense of the term. For the moment, I will assume that all assemblies are placed in the directory of the executable. For Net Core programs, this is already done with a “publish”. I will need to figure out a good solution for Net Framework programs.
- DNA does not seem to handle a number of Net Standard and Net Core assemblies: netstandard.dll (contains table type ExportedType), System.Numerics.dll (machine type 0x8664). This is the most critical problem, since it blocks execution of Net Core–and hence, an important aspect of Campy.
- DNA does not handle type forwarding from Net Standard to the referenced assemblies. In other words, a type may be referenced, but the meta says it’s defined in netstandard.dll. That DLL is a front for the real implementations of the framework used. I’ve identified MetaDat_GetTypeDefFromName() in MetaData_Search.c that should be modified.
Back in October 2017–which seems so long ago, but has been only 8 months–I was looking around for a NET runtime to use for Campy. It was apparent that in order to support C# on a GPU beyond value types, I was going to need a NET framework runtime. Why? It turned out there were many calls into C code, which depended on what runtime the program was compiled against. Even if you ignore this, you still need a meta on the C# side of Campy in order to get the size and alignment of fields in value and reference types when you allocate and copy objects from the CPU to GPU. The JIT compiler has this sort of baked into the code already, but it still needs to be formally added.
So, like any good programmer, I looked around. What I found were big, bloated packages: Mono, CoreCLR, etc. The NET framework that Campy needed I assumed would be a very small substituting layer for only the lowest layer of classes. Understand that GPUs don’t have file IO, don’t have threads in the classic OS sense, and many other things. So, the assumption here is that the lowest level layer isn’t changing, and hasn’t changed for a long time. Therefore, any class that uses the lowest level layer isn’t going to have problems calling into that layer because it is probably the same everywhere. Whether this assumption remains valid only time will tell. And, I can always use one of those bloated frameworks if my assumption is incorrect. But, there were greater problems–like writing a compiler for CIL, so I went fishing.
I came across an article in CodeProject, DotNetAnywhere: An Alternative .NET Runtime. Despite it not being modified for six years, I was heartened to learn that another project called Blazor was using DNA. (I learned a few months ago that Blazor switched to Mono two weeks after the CodeProject post.) So, I decided to port DotNetAnywhere (DNA) to CUDA. That turned out to be not terribly hard, but then I discovered the really big problems: DNA does not work in 64-bits, and there are quite a few bugs in reading the metadata tables. While I congratulate Chris Bacon for writing a good tool, DNA has a lot of problems. I fixed the code so that it runs on a 64-bit target. But, if an assembly contains metadata tables that aren’t supported by DNA, it craps out. And, I just found out that if I declare a field as an array of System.Numerics.Complex, DNA says the type of the field is an SByte!
At this point, I’m kind of committed to using DNA for Campy. I will be fixing the code that reads PE files, including code to read all tables in the ECMA 335 spec, and parsing the signature blobs robustly. I will also be writing a tool to read and output in a human readable format NET assemblies, similar to DotPeek, but with output to stdout so it can be used as a regression tool. As an old coworker said long ago about software: sometime you just have to pound it into submission.
The next release or two of Campy will be hammered out over the following weeks.
One will deal with the implementation of C# generics, which regressed a few months ago after the move to the GPU BCL reference type allocation. It kind of didn’t work all that well, and was a kludge, so it needed to be rewritten. Further, much of the BCL uses generics, e.g., System.Console.WriteLine(), so this must be sorted out as soon as possible.
The other will deal with Campy on Ubuntu. There isn’t any really good reason why Campy cannot be run on Ubuntu, so that also will be fixed. There is already a build for Swigged.LLVM for Ubuntu, and there will be a build of Swigged.CUDA for Ubuntu shortly. I’ll also need to get the GPU BCL of Campy to compile on Ubuntu, but it shouldn’t be any harder than the previously mentioned libraries.
I’m not sure which feature will come first, but generally speaking, a new version of Campy should be available every few weeks.
- Support of enum types (13).
- Performance improvement in basic block discovery of kernel code (77cee89).
- Fix to GPU BCL type system initialization (14).
- Partitioning the build of the runtime from the compiler so that it can be built for Linux. Adding in Linux build. There are a number of ways I’m looking into how to do the build, including the Linux C++ build feature in Visual Studio.
- Rewriting the compiler so that phases are chained methods and renaming the phases that indicate what each does.
Campy version 0.0.8 has been released. The changes to Campy since release 0.0.7 have centered around the integration of the GPU BCL (i.e., the “Dot Net Anywhere” runtime that is being used on the GPU after porting to CUDA C) into the compiler. Unfortunately, the effort has set into motion a large number of changes. Some of those changes I expected, but many were not.
- Up to now, C# objects on the GPU were allocated using a “malloc” of pinned memory. This memory was allocated in C# on the host CPU using Cuda.cuMemHostAlloc(), and is accessible on the CPU and GPU. But, C# objects are managed, meaning that the BCL should know the type of the object when a pointer is passed to it. With the recent changes to Campy, C# objects accessible on the GPU are now allocated using the GPU BCL. (987209c and others).
- The GPU BCL needs to be accessible on both the GPU and CPU because the memory allocation on the CPU needs to be recorded by the GPU BCL. Considerable time was devoted to figure out how to write C# code to call unmanaged C code in a DLL that contains the GPU BCL (example). For the GPU, a static .LIB file is generated that contains pre-linked code (via nvcc -dlink). For the CPU, an unmanaged layer written in C/C++ is provided in a DLL. C# code calls the DLL API using P/Invoke.
- The assembly containing the kernel needs to be loaded by the GPU BCL. Campy “worked” before but used the meta only on the CPU side (using Mono.Cecil). The GPU BCL now reads the meta for any assemblies referenced.
- Even though Campy is supposed to be Net Standard 2.0 code, “dotnet build” of Campy wouldn’t build. As it turned out, Swigged.LLVM and Swigged.CUDA contained references to native libraries which prevented building Campy via Dotnet.exe. Those packages have been updated so that the native libraries are now in the proper sub-directory (Swigged.LLVM 6.0.5; Swigged.CUDA 9.185.5).
- The pre-build code in the .TARGETS file of Swigged.LLVM and Swigged.CUDA don’t work with “dotnet build” because Dotnet does not create output directories before the running the pre-build steps. The build now performs a copy using a completely different Msbuild mechanism.
- This release fixes line-oriented debugging of kernel code (11). Due to quirkiness of Mono Cecil (2116ef7), method references in CIL call instructions would not have debugging symbols loaded. A problem in instruction discovery (IMPORTER) existed with CIL call rewrite: the offset of the instruction was not set. These problems are now fixed.
- Many compiler warnings were cleaned up. A Dotnet build of Campy is completely error and warning free.
- Note, Dotnet works in a different directory from the application that you build. In order to find all dependent dlls and libs, you will need to change directory to the application, or “publish -r win10-x64” the application. Finding dlls is still a mess, but Campy with Net Core and Net Framework does work.
- Nsight does not work with Net Core apps. I have no idea why Nsight is so messed up. Build the application as a Net Framework app, and it’ll all work as expected. Make sure it’s a 64-bit app you are building; Campy only works with 64-bit apps.
The release is in Nuget.org. You will need Net Core 2.0 and have CUDA GPU Toolkit 9.1.45 installed on Windows. To take full advantage of Campy, e.g., debugging with Nsight, you will need Visual Studio 15. You should be able to use the latest version of Visual Studio, although I haven’t tried because the GPU toolkit compiles C++ with VS version 15.4. Dotnet published Net Core 2.0 apps should run with only the GPU Toolkit installed.
Release 0.0.7 of Campy fixes multidimensional arrays and adds simple line/column debugging information to the generated code.
- Implement line debugging of kernel code (4c15bda, b7). Note, there are bugs still in the implementation: it works only for straight line code, no branching (see bug entry). This will be fixed in the next release. Also, you will probably need to use the “Start CUDA debugging (legacy)” menu command of the NVIDIA Nsight debugger version 5.5. The “Next Gen” debugger works only in TCC mode. Looking forward to NVIDIA allowing for combined CPU/GPU debugging in the future. Make sure to follow the instructions for Nsight. Set breakpoints in your C# kernel code before you start.
- Fixing 2D arrays (1284d27).
- Fix “ceq” instruction code generation (75b990d, b9). In certain situations, the compiler would generate incorrect code.
The release is in Nuget.org. You will need VS2017 15.4.5 and CUDA GPU Toolkit 9.1.45 installed on Windows. However, once developed, you only need the CUDA GPU Toolkit installed on the system that has the GPU card.
Release 0.0.6 of Campy improves on the correctness and stability.
- Implemented “ref” parameters for methods (bb4062f)
- Corrected the semantics of Campy.Sequential.For() (9f14e66)
- Fixed GCHandle.Free() of an uninitialized handle (fba9328)
- Fixed API for memory management (4ede1e1 and others)
- Added several examples for sorting to the unit tests (Comb, Bitonic, Even/Odd)
The release is in Nuget.org. You will need VS2017 15.4.5 and CUDA GPU Toolkit 9.1.45 installed on Windows.
I’ve added a Mantisbt bug tracking server for Campy. The server is located here. If you’d like to add to the database, please sign up for an account.
I have just released a new version of Campy. This version incorporates the new GPU Base Class Layer, value and reference types. It does not handle generics, as in my reorganization, I broke that feature; It does not handle ref parameters. To try out Campy, you must use Visual Studio 2017 15.4, an NVIDIA Maxwell or newer GPU, and CUDA GPU Toolkit 9.1.85. I do not check these in code, so please make sure you have the prerequisites. Create a NET Core 2.0 application and add Campy from NuGet.org. For examples of Campy, see the Campy tests directory, such as Reduction. Undoubtedly, there are bugs and it is somewhat slow due to JIT compilation at the time of the Parallel.For() call. You can improve the JIT speed if you “warm-up” the GPU using the kernel code first since the JIT object code is cached for subsequent calls. –Ken Domino
Here is the latest on Campy:
- The base class layer for the GPU has been fixed so that there are no longer any global variables. C++ global variables are initialized when Campy JITs a kernel. In order to avoid re-initializing the base class layer, all but one C++ global variables are now placed in a structure. The runtime is now initialized only once, a critical performance improvement.
- An API for managing GPU/CPU memory synchronization has been added. Previously, after each Parallel.For() call, memory was copied back to C# data structures on the CPU. With an explicit API to synchronize memory copying, certain algorithms, e.g., FFT and Reduction, which nest Parallel.For() calls with another loop, are now much faster.
- I have spent a lot of time making changes to Swigged.LLVM, which is used by Campy. Swigged.LLVM is now fully built automatically in Appveyor. And, it has been updated to the latest release of LLVM, version 6.0.
- I will be making a release of Campy in the next month if all goes well.