 I feel like absolute shit. I don't know what the hell happened last night, but I woke up after only sleeping for like two hours, had an incredibly difficult time going back to sleep and whatnot, so... Yay! I'm going to do an easy one this time. I, uh, like to talk. Hardware support and intrinsics. A lot of individual things that we can cover for this one. Each one, pretty small, but quite a few different things that go into affecting this. First and foremost, and very easy one, very little to mention for it, is representation clauses. Contrary to some of what is said on the Aida wiki books by some of the Aida community, Aida is not the only language that supports that, although by a massive margin, Aida has the most sophisticated implementation of it. .NET does support attributes to allow for field offsets. Now those are done at byte boundaries only. Although to be perfectly honest, that is adequate for the overwhelming majority of situations, so that's good enough for most people. And considering .NET really isn't meant for direct hardware control, it's totally fine for anything that you actually would be reasonably using it for. What Aida supports, though, is the ability to define a record representation clause that does the field offsets right down to the size of any particular thing, the exact bit location for any particular thing, and packing them as necessary, adding in all the associated code to support packing and unpacking. This means you can set specific Boolean flags for specific bits, and that's actually really useful. You can set something that really only needs three bits of data to exactly three bits. Take another three bits and use them for something else. Why three bits? It's actually pretty important when it comes to binary coded decimal, or does BCD use four? No, BCD uses four. I'm thinking of something else that uses three, but still, record representation clauses make it easier to code for BCD is, you know, it's handy. Oh no, the one with three bits is a network protocol, I'm thinking of, an obscure one at that, never mind, but still, those clauses, much like I just mentioned, tend to be incredibly useful for specific layouts of network packets, but also for hardware. You can set a record as the payload that would get sent to a set of bits, because the whole thing gets streamed to a particular controller, other applications for it. That's one such option. Have it represent the, I don't know, you can't really do that, because again, it doesn't support properties, so you wouldn't be able to have a getter, setter, behavior, it's not going to work, but now you could have it represent the payload that gets sent to a controller. Don't need to do any fendangling, you can, you know, it already would have the exact layout that the controller would expect. That's handy. It's not too useful outside of that, to be perfectly honest, but I mean, when you need it, when you need that, that makes it immensely easier than anything else I've ever seen. Another thing to consider is flags. Flags being a specific type of enumeration, in which the enumeration values refer to specific bits within the backing data store. The backing data store will typically be a 32 bit integer, but can be of different sizes. Now you can get flags like behavior out of records in IDA, and you, there are things that you can do to get to a very flags like experience. It's clear that IDA has its own unique way of doing it, and it's not a bad way, but it definitely involves more typing, more setup, and requires an awareness that other people wouldn't expect, and I don't really consider it justified in many cases, in the majority of cases really, because flags are just convenient. You write the specific ones you need, you order them together, and you just have it, yeah. You can create flags through a different approach in IDA, using a specific field set like offset stuff. You can do it. I strongly recommend, if you implement the thing, that you use a view overlay and then actually do your bitwise logic so that it's more performant and the, yeah, you gotta fuck around with that, which is remarkably unsafe code and difficult to do, but you can do it. Programmers that don't feel comfortable doing that would still be able to go the more safe at a route. You can't really consider any clear winner there, and it's not like either is suited for specific niche, they just have different ways of going about it. One really isn't better than the other, but our numerations are important for, or flags specifically, are important for so many different things when it comes to hardware. There are so many situations in hardware where specific bits are used to identify things rather than an entire Boolean value, which is often an entire 32-bit value because, yay, space efficiency, but processors often find it faster to load a full 32-bit value rather than a partial 8 or 16, so that's the justification for it. It's very, very useful when you need to load a collection of Booleans all at once because, again, you can store them as a 32-bit value, that's the fastest for processors that load typically. Loading 32 Booleans at the same time, well, that's fast, that's very fast. You just cut your operation time down by a 30 second, it's worth using flags when they make sense. Another factor, of course, is your access to hardware-specific types. Aida has this, it's weird about it, but Aida has this. You have to do a little fendangling to get the 80-bit float type that the 80686 and its descendants have, but conveniently, that is the standard long, long float, so you still have access to that. On .NET, the whole thing's a little weird. Each of them has a declared minimum precision, but are you going to get a, if you use a double, are you going to get a 64-bit or an 80-bit float? It's hard to say. I'm not super familiar with the rules, although I am familiar with one thing. Even the 80-bit float is starting to not be recommended anymore, because it's not super useful when it comes to any type of distributed heterogeneous computing situation. Sending a single or a double 32 or 64-bit float across processor architectures generally works pretty well. Both are pretty well supported, but the 80-bit float that the X86 line has is really not. If you're only doing localized computing, Aida might win out a little bit for that, but it's not a huge deal to be perfectly honest. Even then, there are situations where people use floats that a different numeric type would really be more suitable. C-Sharp's decimal type is actually more sophisticated and more precise than any of Aida's ordinal types are. Aida's ordinal's being a base 10 fixed point type, rather than the standard base two fixed point types. Reason for that is to do with the incredibly large size of the C-Sharp decimal type. If I remember my stuff right, it's like 96 bits with the upper half being used for the whole part and the lower half being used for the fractional part, but it's a large value with a tremendous amount of precision. There's a standard set of libraries behind it, and it's easy to code for. Being said, if you need smaller, sized base 10 values, that is justifiable sometimes. That it does make them easy. Clear winner there? Not really. Not really. Lastly, we've got access to intrinsics. It's being something that is defined within the compiler or application virtual machines are weird. Technically, it could be defined within the VM or as part of the JIT as well. It doesn't necessarily need to be defined in the compiler in that instance, but it's something that's not defined as part of the standard library, but can be called exactly as it was. These are actually used a lot in the .NET runtimes because they allow for native-like performance because it is native. The .NET string type is a particularly interesting example of that, where it's not actually defined in the .NET space. It's not just an array of character. It is, but it's not defined in .NET. It's wholly managed by something else entirely. It's typically written in C++, but it doesn't have to be. It's native code. Then because of very tightly defined contracts, you don't actually have to do marshalling. I believe they do F call for that. I don't need to get into details of that, but it's a specific call thing that's really fast because you can avoid marshalling. It's meant for inside the runtime itself. But it's used outside of that. Vectors are a fantastic example of it. The .NET vector is being a type that was introduced fairly recently, but it's meant to represent whatever the hardware can do regarding the type that is passed into the vector. And there are rules about it, despite vector being a generic type. You can't instantiate it with just anything. It has to be one of the primitive types. Actually, no. It's even a subset of primitive types. It has to be one of the, what do they call that? What the hell is the name for that, actually? Is it primitive? Regardless, it has to be one of the core numeric types or character, which really is a core numeric type anyways. I don't know, maybe character is not allowed, but it's meant for SIMD operations, a single instruction multiple data. You've got specific constraints on the vector, depending on the hardware you're using. So you probably should get hardware specific information before just filling it up because you could fill it up with too much data. That's going to depend on what you're running on and is not agnostic. So you can't do the typical .NET thing where you just write the code and it works on anything. You will need to check the capacity of the vector for the type that you put in. But once you have that set up, you can just fill it up, do your vector operation, and what actually gets executed is not .NET code, but the intrinsic, which actually calls the SIMD operation. Now .NET, for the most part, only supports the x86 and ARM. So this would mean the SSC or AVX instructions for the x86 and what the hell is it called I remember Thumb and Giselle. I don't think it's either of those though. Oh, shit, I don't remember. But either way, ARM has its own SIMD instruction set and as long as they are supported, it will call those. There is a fallback mechanism, which does the right thing, but as slow. This should sound familiar because I was doing a very similar thing with EDA, where there was a default package, which did the universal right thing, but was slow because it iterated through and didn't actually do SIMD instructions, but then you could provide architecture specific packages that would do the SIMD instruction. Same idea. The only thing is how they accomplish that. I had to accomplish that by swapping out package bodies, providing a different library. The net runtime is able to do that by the application virtual machine that the code is running on. The AVM can figure out whether or not it can actually utilize those, and see if it can, it does. There are other instances where intrinsics are used, of course, but that gives you an idea of how they're done. EDA tends to provide different runtimes for everything that it runs on, and that's why providing a different package body lets largely work. You just switch to a specific runtime for the exact hardware you're running on. That approach is also fine, and there are even some reasons why I think that approach is actually a little bit better, but it's such a minor thing, and really just has to do with how the code is actually implemented, and the simplicity of managing the code, and very little to do with anything the end user experiences, the downstream developer. So I can't really call a clear winner there, plus I can only think of how I would implement that in an application virtual machine. Microsoft or the .NET foundation might have their own implementation that might be just as easy to implement as the package body switching that I worked out. So I can't call a clear winner there either. One thing I can say, however, is that when it comes down to you needing to put in your own hardware specific calls or operating system specific calls, C-Sharp is a clear winner. C-Sharp being essentially refined improvements to C++ Java, focusing more on the C++ side of things than the Java side of things, but there's inevitably inspiration, and they fully admit that. It uses something that kind of looks like a preprocessor. It's actually part of the language itself. It is a formally defined part of the language, the preprocessor part of it always runs. It's even called the preprocessor, but it's not a preprocessor. It's part of the language itself. If you implement a C-Sharp compiler, it has to implement that. It is a full part of the language, not its own program. You cannot call the C-Sharp preprocessor on other stuff like you can with the CPU processor, because that's that's its own thing. This means that there's no support for macros and a bunch of other stuff, but there is basic support. The parts of the CPP that made sense, like conditional compilation, they do exist. You're limited in what you can do with them. They have to use a defined constant. Those constants can be defined in source files, or they can be defined in the project files, or they can be passed in at the command line. Defining them in project files based on certain project configurations opens up very useful possibilities. See often, even in the implementation for hardware specific stuff, there's only small portions that actually deviate between architectures. You can typically share the overwhelming majority of the code. Do you really want to provide an entirely new body for each architecture, or do you want to just switch out the few lines that are different? With this in mind, I think targeting specific behavior, C-Sharp actually wins out on this one. Furthermore, well, not a specific .NET thing, but still so tightly integrated with the ecosystem at this point, so expected to be used that every IDE supports it now, and hell, even the .NET tool itself requires it to be there and utilizes it, new get to packaging. I've said it numerous times in previous videos, and it doesn't have a package manager that it can use. Considering my work has entirely stopped on that front, that means Alire is probably going to be used as the package manager in time, but Alire has, well, that's a video for another time, but new get has addressed a lot of these issues. It's not just a throw your library in there and download it kind of thing. They've worked out a lot of these details, because especially with .NET Core and breaking up the runtime into individual components, it's individual, separated as possible. There's a lot of new get packages that contain parts of the runtime, and they're hardware-specific. There are ways of taking multiple different assemblies, different libraries that target different hardware, and putting them all into the same new get package, so that when the new get package is referenced and downloaded, your system grabs the exact assembly that is appropriate for it with a fallback that is to be used by everything if there is no platform-specific stuff there. That is fucking awesome. All that really needs to be done to make this an absolutely beautiful system is to have IDE support for doing this for different operating systems and different hardware. Currently, these systems are in place to do this for different .NET runtimes. You can specify in a project file that there are multiple target frameworks, and it will build the assembly for each of those target frameworks. You can have your conditional compilation for each of those frameworks, and it all just works. When you reference the assembly, it references the most up-to-date target that it possibly can from within that new get package. Being able to do that with the hardware or operating systems would be fantastic. Now again, you can build that new get package yourself, and it will happen. New get itself will take care of this. It's just building that automatically for you, adding the necessary project flags or project tags, rather, to take care of that and whatnot. That's really the only thing that's lacking, but that's minor. The entire rest of what's necessary is there. So in my opinion, there's a clear winner there. It's way easier to target specific stuff in C-Sharp, despite C-Sharp doing nearly everything possible to keep it from being hardware specific. .NET is an abstract platform that still targets specific hardware better. It's just bizarre, but packaging is important. It's kind of unreal how much good packaging actually helps, and how much non-packaging things are actually affected by packaging. So that's it for this video. Have a good one, guys.