Home-made compiler and game library Raylib. Docking experience

image

They say that the success of a programming language or compiler depends largely on its ability to interact with third-party code. Of course, the "success" of an amateur compiler must be understood with a certain amount of conventionality and even irony. However, here, integration with external libraries written in C can become a good school of life.

About my compiler XD Pascal there were already several posts on Habré. The compiler is written extremely simply and entirely manually, while the language has very atypical extensions - methods and interfaces borrowed from Go. To date, the base language is fully implemented, self-compilation works, the simplest optimizations are introduced. Here a natural desire arose to establish the interaction of the compiler with some simple game library. The choice fell on Raylib - but he would never have fallen on her if I had foreseen her pitfalls at once. An innocent venture turned into a fight against challenge agreements.

Devil in detail


I liked the Raylib library because it is relatively small, actively developing, almost no external dependencies, and has a ready-made binding for Pascal . In addition, all material arithmetic in it is of single precision. Unfortunately, this is relevant for me, because double precision has not yet appeared in my XD Pascal. ( Addition: double-precision arithmetic is implemented).

At first, it seemed like a little was required of me - just to implement the cdeclRaylib calling convention. stdcallI already had support , because I had to interact with the Windows API. It only remained to learn how to clear the call stack, not inside the function, but on the calling side.

Then a strange thing was discovered. Some kind of diabolical force forced the author of Raylib to use in his API the transfer of structures into functions by value and the return of structures as a result. It has been said more than once that this is bad practice, and to the credit of the developers of the Windows API it must be said that they avoided this in every possible way. But not the Raylib developer. From here many problems arose - both objective and subjective.

Passing structures by value


This problem is rather subjective, although not all. The fact is that my XD Pascal was designed from the very beginning to generate code on the fly, without explicitly constructing an abstract syntax tree (AST). In my defense, I can only say that all the early Pascal compilers were the same, including the unforgettable Turbo Pascal, and the language itself was designed by Niklaus Wirth specifically for compilation without AST.

This approach was quite acceptable until the need to interact with the code in C. The compiler without AST can put the actual parameters of the function on the stack exactly in the order in which they are listed in the source text - from left to right. However, C code with its conventions cdeclandstdcallexpects the reverse order. It’s not a big deal to “flip” the stack if you know in advance that all parameters are exactly the same size (for example, 4 bytes), as is the case with the Windows API. But if structures of arbitrary size appear on the stack, the “flip” of the stack becomes much more complicated. Now we have to put up with him; maybe the transition to AST will one day save me from this absurdity.

Of course, the problem of the transfer of structures has an objective side, associated, for example, with alignment. In both Raylib and XD Pascal, all structure fields are not aligned, and structures as a whole are aligned by 4 bytes. Here, for me, no integration difficulties have arisen, but I will not risk asserting that such an agreement is portable to other compilers and platforms.

Return structure as result


Structure as a result of a function is already a serious and absolutely objective problem. One can only wonder how the IT industry allowed such a blatant chaos, hiding behind the directives cdecland stdcall. The general thing here is only the idea of ​​allocating a place in the stack of the calling party for the result of the function, and then passing the hidden parameter-pointer to the allocated place to the function. But further questions arise, to which each answers his own way. What position should the hidden parameter be in? Does it need to be abandoned if the result structure fits entirely in the register? And in two registers?

Microsoft tried to put things in order by deciding:
On x86 plaftorms, all arguments are widened to 32 bits when they are passed. Return values ​​are also widened to 32 bits and returned in the EAX register, except for 8-byte structures, which are returned in the EDX: EAX register pair. Larger structures are returned in the EAX register as pointers to hidden return structures.

This somewhat vague wording leaves the fate of structures, for example, 7 bytes long, unclear. At the same time, the author of one painfully detailed study claims that the actual behavior of the Visual C ++ compiler in the absence of alignment of structures does not correspond to the documentation at all.

With industrial Pascal compilers, things are even worse. Free Pascal (in Delphi mode) is incompatible with Delphi 6 even when transferring 8-byte structures with conventioncdecl. Free Pascal is trying to follow Microsoft’s prescription - in this case, it’s pretty straightforward. Meanwhile, Delphi 6 creates a hidden pointer parameter and does not return anything useful in the EDX: EAX registers. I followed the example of Free Pascal, since this is the version that is implemented in Raylib. I suspect that working with Raylib from Delphi 6 is generally impossible. I don’t know if something has changed in new versions of Delphi.

Total


As a compromise, XD Pascal implements the following logic of using cdecland stdcall: structures of no more than 4 bytes are returned by value to EAX, structures of no more than 8 bytes are returned to EDX: EAX, all others through a hidden pointer parameter passed by the latter. Fortunately, in Raylib there are no structures of 3 or 7 bytes, so the ambiguity associated with them can be bypassed for now.



The Raylib library as a whole successfully docked with my homemade compiler. A fly in the ointment remained the only function GetTimethat returns Double. ( Addition: function is now supported).

All Articles