S02E15 51mSeason 2

Sponsor Message
This episode of The .NET Core Podcast is brought to you in part by RJJ Software
RJJ Software is dedicated to helping you to realise your company’s digital potential through innovative solutions using the latest technologies.
Utilising the latest in .NET and cloud technologies, we can help you build the solution to your business needs, on time and under budget.
We have world class experience in cross platform development using the latest iterations of the Microsoft technology stack. This includes .NET Core; SQL Server; and Azure. Is Azure not your thing? That’s fine, because we have experience with AWS, GCP, Linode, and Digital Ocean, too.
We know what it means to have to be able to iterate quickly, and how important DevOps practises are when it comes to remaining Agile. As such, we have experts in the major build and release pipelines: from Azure DevOps to AWS, from Netlify to TravisCI, AppVeyor, and everything in between).
So get in touch today at RJJ Software, or check the show notes for a link.
Episode Transcription
Hello everyone and welcome to THE .NET Core podcast - the only podcast which is devoted to:
- .NET Core
- ASP.NET Core
- EF Core
- SignalR
and not forgetting The .NET Core community, itself.
I am your host, Jamie “GaProgMan” Taylor, and this is episode 47: Hacking .NET with Michal Strehovský. In this episode I interviewed Michal about his work with the CoreCLR, CoreRT, and some of his most recent blog posts - including one where he built and ran a .NET Core app on Windows 3.1. Some of you may know Michal from his work at Microsoft, or from his open source work on the CoreRT runtime.
So let’s sit back, open up a terminal, type in dotnet new podcast and let the show begin.
The following is a machine transcription, as such there may be subtle errors. If you would like to help to fix this transcription, please see this GitHub repository
Jamie
Michal
Jamie
Michal
Jamie
Michal
Jamie
Michal
Jamie
Michal
Jamie
So yeah, totally makes sense. Why spend a huge amount of resource on re-implementing the exact same code again, with all of those bug fixes when it already exists?
Michal
Jamie
Michal
Jamie
dotnet build and had a look at their binaries and supporting files, you should see that that is absolutely amazing. Would you mind telling us a little bit about that and like what made you want to do that? Why go through the effort of seeing if you could get an eight kilobyte binary, and all of the research you’d have had to have done to get to that point?Michal
So when I’m experimenting with these things like that the snake game, I’m just trying to unlock what is already there in C#. Because often times people say that. oh, C# is not a systems programming language as Go is a systems programming language." But both languages are kind of very similar. C# is just better, C# has more stuff, but if you don’t use it, you can go down to the sizes that Go has and you can go down to the memory usage that Go has. So that’s kind of what I was playing with when I was in the snake game. So the reason why it was maybe so challenging is that .NET, or C#, is traditionally a VM based language. So when you compile your C# code, the result of it is something that you cannot directly run on any computer, because it’s something that is called byte code: it’s instructions for a processor that doesn’t actually exist. It’s something abstract. And in order to run, or to interpret that kind of code, you need a virtual machine. So a virtual machine takes the output of the C# compiler, and kind of makes it possible to execute the semantics that are in there, the instructions. So if you write a C# game, the output will be relatively small, but you need a huge virtual machine that has a garbage collector, that has [a] just in time compiler, that has all the class libraries that you are using. And the end result will be something that is big.
But if you say, “Okay, I don’t want to use all those things that .NET is offering, I would like to restrict myself to the subset that is low level.” What can be the result of that? Like, can the size be comparable to if I was writing it in C? And The result of my experiment was that, yes. Like, if you restrict yourself to that subset, the result will be really the same as if you wrote it in C. And so the question is, “okay, why would you even do that? Why would you restrict yourself to a subset of C# that is kind of like C because then you could just write your programme in C?” And the answer is, “basically, if you are a C# programmer, switching to C has overhead,” like, if you switch from C# to C, then you have to start thinking, “Okay, how do I write unit tests for this thing? I know xunit from C#, but what’s the equivalent in C? How do I find out what is the code coverage of the game logic that I wrote? What’s the tools on the C side for that?” if you stick with C# for these things, you are still using C#, it’s just you’re using a subset of C#. But all the tools that you have for C# language are available to you. So even though you’re writing something low level, when you want to test it, you don’t need to be restricted by those low level restrictions. Because you are only targeting the result, you only want the results to be small, you don’t care that when you are testing the app, it has 50 megabytes or hundred megabytes because it has to carry the whole virtual machine with itself. Yeah, that’s what I was trying to see. If that makes sense.
Jamie
Reading through the article there’s talk of, “Yeah, if I use this linker and implement fake implementations of things. So like, here are a bunch of structs, which represent the built in types, which then reduces the requirement of having to pull more of the runtime and just all of that stuff.” It clearly shows that you work on this on a daily basis.
Michal
So IL Linker can look at what the app is actually using, and can remove the libraries that are not in use. So you don’t use regular expressions, why ship them? You don’t use Linq, why ship Linq? So that part is something that anyone can try and use right now. Like if you are a .NET Core developer, you can use IL Linker to make your app smaller. The next step of the journey was to have a look at an experimental runtime that Microsoft has. And this experimental runtime is specialised for ahead of time compilation. And it compiles only the parts of the app that are used. And that also applies to parts of the runtime. So if within the runtime you don’t use, let’s say reflection, you don’t actually have to compile it into the app. These kind of levels of what’s available to you , that’s what I find pretty interesting. Because you start with all of .NET, like everything is available to, you have a really comfortable development experience. But the result might be not so small as it could be, what you’re getting in return is acceptable activity. And then you can say, “Okay, what are the things that I can sacrifice to get something more?”
Jamie
Deserialise<T> will it recompile json.net? So I just get that, or is it just my code and the framework?Michal
Type.GetType() API with this text string, and you will get a System.Type instance back. And this System.Type instance lets you do stuff with it like, “oh, make a new instance of this thing,” or, “find me method with this name and then invoke it with these parameters.” The thing is that this is really difficult to analyse, because the text string could be literally downloaded from the internet. So if you are trying to remove unused parts of the programme, you don’t really know which parts are unused because the app can just do reflection and take a text string and find something that was never used within the app. And if the thing was removed, this reflection call will fail. [The] .NET team has been playing with this for a really long time, and there is no good solution for this. The real problem is that that .NET has design constraint reflection. And because of that, you don’t really know which parts of the app are unused.Jamie
Michal
Assembly.Load() API with a text string that specifies the assembly that it would like to operate with. And if that assembly was removed, then this call will fail.And we have also seen this to be a problem in unexpected situations. For example, serializers: serializers are notorious for using reflection to do things. So if you would like to deserialize something, for example, you have a piece of JSON and you would like to deserialize it. That’s precisely what the deserializer is doing. It will look at the text string within the JSON will try to find a property or field with the given name on some type, and then it will try to kind of reflection set the fields to the value that the JSON specifies. The serializers sometimes capture the name of the type as well. And along with the name of the type you also need to know the assembly where the type is coming from. So the serializer will use the
Assembly.Load(), or it will call Type.GetType() with some name of the assembly or like assembly qualified name. And even in that very conservative situation where we only removed assemblies that we thought that are not used, this would still break. It’s really hard to find the right balance where we know that we only removed code that is not used. And you don’t have to worry about your app breaking if you start doing this.Jamie
Assembly.Load(). It would literally do that Assembly.Load(), find this DLL, find this method within it, and pass this data in and this data will conform to some object that that particular assemblie’s load method would expect. So in some instances, it would pass a string in some instances it would pass an object of a specific type like a custom object. Sometimes it would just pass an integer.And yet, as an engineer of the IL Linker, that would be impossible to predict, it’s not going to be possible to figure out that, “oh, yeah, this particular app called this particular name is going to look in this particular directory for a bunch of DLL files, it’s then going to use reflection to figure out where the objects within that DLL exist, and where the methods exist. It’s also going to try and create a bunch of instances of those objects and call this method which you don’t know whether it exists.” So absolutely, I totally get that it is a difficult problem to solve. And although I have a background in computer science that is way over my head, so I totally get why that’s a difficult thing to solve.
Michal
Sometimes I feel that it’s kind of unfortunate that all the developers got conditioned to the framework being always available, and to using these patterns, because then it makes it harder for us as runtime developers to make things lean and small and efficient. These patterns for example, dependency injection engines, very commonly use patterns. They will just look at all the files on the file system next to the app that you’re running. And it will just open all the assemblies look at all the types. Make a catalogue of like, “Okay, this type implements this interface.” And then at some point, the app would say, “Oh, I would like to instantiate the thing that implements interface IFoo.” And then the output consult this catalogue, and it would create an instance of an object and implements that interface on demand. This is really hard to support even in the modes that we are thinking about adding to .NET Core or we have already added.
Jamie
Michal
Jamie
Michal
Jamie
Michal
Jamie
One [is] don’t include the tracking stuff. If you go to any of the websites that I’ve made for this show, or some of the other shows, there’s no tracking involved. Because I really don’t care. Most of the time, people will collect all this information and not do anything with it. And the second one is make sure that it is completely separated, don’t have a dependency for your web app, for your website, to have the scripts loaded. Because the second that they are blocked inside of a browser using ad blocking techniques, somehow maybe it’s inside the browser, maybe the DNS level. Soon as you do that the website is non functional. Just a little bit of friendly advice on website content.
But you touched on something earlier when we hinted at talking about Blazor: these constrained environments. And I know that’s one of the things that you kind of have a passion for. In fact, one of the other blog posts I read of yours was, “hey, let’s get C# running in Windows 3.1.”
Michal
CoreRT looks at it differently. CoreRT tries to take the IL that the C# compiler produces, and convert it into native code that then can call into other libraries when it needs to do something special. So the special things could be, “I would like to allocate a new object.” So the native code that the CoreRT compiler produces will have machine instructions for basically everything that you set in your C# programme that you would like to do. And whenever it needs to do this special thing like allocate an object, throw an exception, or perform garbage collection. It would call into a separate library that would help it to this task.
The funny thing, once you structure a runtime this way to kind of not make it a runtime but to make it a runtime library, is that you can provide your own implementations of these things. So you can say that allocating a new object will not do the thing that .NET usually does, but it will allocate a piece of memory on the managed heap and the garbage collector will track it. But maybe it will just allocate a piece of memory with malloc, and the memory will never be free. So I was playing with these things. And then I kind of realised that if I can convert the C# app into something native that sometimes needs the help of a runtime, but most of the time, it doesn’t, what are all the places that I can make C# code run on? So I made this experiment where I made C# code running and Windows 3.11. Because we’ve restricted ourselves to something that is easy to satisfy on Windows 3.11. Like, for example, you say, “Yeah, I don’t want to use garbage collector, I don’t want to throw exceptions, I just want to show a message box.” The code generated by the native compiler that CoreRT has, will not actually have any calls until the runtime. So the output of the C# compiler will be a binary that kind of starts right there where your C# programme starts. And it will do a P/Invoke into the show a message box, and it will exit. And it’s as if no runtime was really involved.
If you wanted to do something similar with CoreCLR. CoreCLR also has an ahead of time compiler, but the outputs of the ahead of time compiler need at runtime because the operating system doesn’t know what to do with them. The EXE that the head of time compiler of CoreCLR generates it not the standard format of executables that Windows knows how to deal with.
If you have a VM, the process always starts in the VM, and the VM loads the programme and then starts executing the instructions. If you have an ahead of time compiled version of .NET, the process starts with your code. And if it needs some kind of high level runtime service, then it will call into the runtime. If that makes any sense.
Jamie
Michal
Jamie
I know that there are enterprises and there are systems out there that run on these constrained environments, perhaps the older operating systems, or perhaps IoT devices, or perhaps really specifically designed constrained systems for security reasons, for backwards compatibility reasons. So yeah, being able to - I don’t want to say quickly throw some things together in C#, and quickly push some buttons because clearly reading through those blog posts and listening to you describing the process there, it takes a lot of effort. But being able to throw something together that will do something in C# land, or C#-like land on those constrained environments is, it’s amazing. I don’t think anyone back in 1999/2000 when .NET was still on the verge of coming out, and people were trying it out. If somebody would have said, “in roughly 20 years, you’d be able to run this on 3.1, or indeed run this on Linux or Mac OS, or on these tiny little single board computers you can buy for $25.” It’s amazing.
Michal
Jamie
Michal
Jamie
.NET: write once, run anywhere
and it is literally true.
Michal
Like those two projects kind of phased out, because I guess the compact framework became less relevant when the chips started to be more smart. The middle tier became the higher tier, or kind of the higher tier became the middle tier. You can now run .NET Core on the $20 boards. You no longer need to put Windows CE and compact framework on it, because it will just run the full thing it’s powerful enough for that. Yeah, the micro framework, I think there are still some people who are investing in it and are playing with it. I think that it was open sourced.
Jamie
Michal
Jamie
Michal
Jamie
Michal
Jamie
But what I’m getting at is the very first time that I was able to compile something on my Mac OS laptop and push a button, and it be transferred over to the Raspberry Pi. And I had this Raspberry Pi connected to a 40 inch TV, and I had a little keyboard and I typed in
dotnet run and it said, “Hello, World!” And that was a magical moment for me.Michal
Jamie
Michal
Jamie
Michal
Jamie
Michal
When you look at CoreRT, it’s based on .NET Native. Which is something that has been powering the Universal Windows apps ever since Windows 10. They kind of couldn’t make .NET Native, as it was, it was open source. Because .NET Native uses UTC as the code generator. UTC is the the backend that [the] C++ compiler uses to generate code. So when you’re building a .NET Native app, it gets compiled into native code using the same code generator backend that visual C++ uses. So it applies all the crazy optimizations that C++ does. So when we were thinking about open sourcing that we knew that we wouldn’t be able to open source the UTC backend.
So we looked at CoreCLR. CoreCLR has RyuJIT as the code generator. It’s a pretty solid code generator for managed code. So we took the runtime from .NET Native, and the class libraries from .NET Native, and we mixed it together with RyuJIT from CoreCLR. And that’s how CoreRT came up.
It’s basically the experimental ground for us to look at full ahead of time compilation. I like to use it for my side projects and for my experiments, because .NET Native executables that that generated by it are the same executable as you would get from a native compiler. So if you write a C app and compile it to native code, you get an exe that the operating system understands - like the operating system knows how to load it into memory, how to start executing instructions in it. It’s different from the executables that Mono has, that kind of need a runtime to kind of load them first, and then they can start running the code that is contained in those files.
Yeah, it’s a really good runtime to play with, because it has a really small core, and then it builds on top of that. So of course, you can run ASP. NET with CoreRT as well. It’s just that’s not the things that I play with in my spare time.
Jamie
Michal
dotnet publish. And magically, instead of the app getting published with [the] CoreCLR runtime, it will get compiled ahead of time with the CoreRT compiler, and packaged into the Publish directory for you.So if you want to play with it, it’s really easy.
Jamie
You mentioned about how there’s lots of technical detail as to how it works. If I want to play with it, and I want to understand a little bit more about what it’s doing, do I need to know a lot of computer science-y things? Or is this more just a case of, “don’t worry about it, we’ll handle the minutiae of how it works?” When I was reading through your blog posts, and listening to now talk about how swapping out the runtime and implementing your own versions of things: so like with the snake game, there’s a point where you say, “oh, yeah, it’s fine. I’ll just implement a bunch of stuff for the framework. It won’t do anything. But here’s a bunch of classes that represent the built-in types.” How would someone go about learning all of that? Is that just because you have the actual experience of building tools and the CoreRT, and you have the experience of working with the runtime, or is that is that something that people can just learn?
Michal
The CoreRT codebase is mostly written in managed code. The pieces that are in native code is the garbage collector - because it’s using the same garbage collector as CoreCLR code generator, because it’s using the same code generators, CoreCLR - and then a couple small things like that supports the garbage collector. So for example, stack walking, which is something that [the] garbage collector needs to do [in order] to find out, “what are all the objects that are being used?”
But besides those things, pretty much everything is written in C#. So, if you would like to see how typecasting works, the typecasting is implemented in C#; if you want to see how Exception Handling works Exception Handling also is mostly implemented in C#. If you are a C# developer and want to get into the internals of those things, the CoreRT codebase is really accessible for that. If you want to look for it in CoreCLR or Mono then you have to understand C [and] C++; might be easier to see it on the CoreRT side.
Of course, the implementation of these things is not [in] idiomatic C# code. Because if you are implementing typecasting in C#, you obviously cannot cast types within the typecasting algorithm because you will get recursion: how will [you] cast types if you are implementing the typecasting algorithms? Or if you are the exception handling, you cannot throw exceptions because then who is going to handle those? It is still C# code and it’s pretty readable.
Jamie
Michal
Jamie
Michal
Jamie
Michal
Jamie
Michal
That experiment is basically trying to translate the apps into C++ code, like C++ source code: You feed it a compiled assembly output of the C# compiler, and then the CoreRT ahead of time compiler runs it and produces C++ code that do[es] the same thing as the C# code did previously. And it will link it together with the CoreRT runtime. They were working on that a little bit. They added support for reflection. There is someone who is trying to add support for webassembly to CoreRT. So there is some community activity as well and we are trying to help if we can. Of course, CoreRT is nobody’s full time project. It’s kind of just a side project that we are doing at nights or over the weekend. Kind of don’t expect immediate answers. But we are trying to help other people experiment with it as well.
Jamie
Okay, what about yourself, then if listeners want to keep up with the work you’re doing and on this particular thing, or learn a little bit more about how to get in contact with you? Is that something that you are okay with, like, is there - I know there’s a Twitter and our blog, but is that the best way for folks to get in contact?
Michal
Jamie
Michal
Jamie
Michal
Jamie
Michal
Jamie
Michal
The above is a machine transcription, as such there may be subtle errors. If you would like to help to fix this transcription, please see this GitHub repository
Wrapping Up
That was my interview with Michal Strehovský. Be sure to check out the show notes for a bunch of links to some of the stuff that we covered, and full transcription of the interview. The show notes, as always, can be found at dotnetcore.show, and there will be a link directly to them in your podcatcher.
And don’t forget to spread the word, leave a rating or review on your podcatcher of choice, and to come back next time for more .NET Core goodness.
I will see you again real soon. See you later folks.
Useful Links
- Michal Strehovský on twitter
- Building a self-contained game in C# under 8 kilobytes
- xunit
- Using the .NET IL Linker
- Tree Shaking in webpack
- TinyOS
- Michal’s Twitter thread on running C# in Windows 3.1
- CoreRT Runtime
- CoreCLR
- malloc
- P/Invoke
- Meadow from Wilderness Labs
- Windows CE
- .NET Micro Framework
- Pi-hole
- RyuJIT
- json.net
- System.Text.Json
- Michal on Twitter