Passing string constant or literal to GCC built-ins in Ada
I've use a few intrinsics before with GNAT, but I get an error for __builtin_cpu_is
when trying to pass in an Chars_Ptr
:
error: parameter to builtin must be a string constant or literal
I also tried plugging the "amd" target parameter in directly, but that didn't work.
with Ada.Text_IO;
with Interfaces.C.Strings;
procedure Intrinsics is
procedure CPU_Init;
pragma Import (Intrinsic, CPU_Init, "__builtin_cpu_init");
function Is_CPU (CPU_Name : Interfaces.C.Strings.chars_ptr) return Interfaces.C.Int;
pragma Import (Intrinsic, Is_CPU, "__builtin_cpu_is");
Target : constant Interfaces.C.Strings.Chars_Ptr := Interfaces.C.Strings.New_String ("amd");
begin
CPU_Init;
-- ERROR from the below line, from Is_CPU
Ada.Text_IO.Put_Line (Interfaces.C.Int'Image (Is_CPU (Target)));
end Intrinsics;
References I've been looking at:
- GCC Built-ins
- Learn Ada, Interfacing w/ C
Solution 1:
I think you hit a (current) limitation in importing GCC intrinsics in Ada programs (at least for version GCC/GNAT FSF 11.2). The best workaround is to wrap the builtin/intrinsic with string literal in a C function and then import that C wrapper function in the Ada program.
The error is thrown by the GCC back-end (see here). The built-in only accepts a string literal. This is not clear from the equivalent C signature of the built-in. The equivalent C signature suggests that any constant pointer-to-char is accepted:
int __builtin_cpu_is (const char *cpuname)
However, a simple test shows that this works:
#include <stdbool.h>
bool is_amd() {
return __builtin_cpu_is("amd") != 0;
}
But this doesn't:
#include <stdbool.h>
bool is_cpu(const char *cpuname) {
return __builtin_cpu_is(cpuname) != 0;
}
During compilation the abstract syntax tree is analyzed and the reference to the built-in is being matched along with the actual parameter that is passed in. This actual parameter must be a string literal (a specific tree node type). The string literal is then parsed/matched by GCC. Upon success, the call to the built-in in the syntax tree is (as a whole) replaced by a comparison (done here).
$ gcc -c is_amd.c --dump-tree-original && cat is_amd.c.005t.original
;; Function is_amd (null)
;; enabled by -tree-original
{
return __cpu_model.__cpu_vendor == 2 ? 1 : 0;
}
Now, it seems that the GNAT front-end is currently unable to generate the exact nodes (or node pattern) in the syntax tree that will match those expected by the built-in parser. This is likely because of the declared signature of the built-in and the fact that Ada makes a clear distinction between string values and string pointers.
The GNAT front-end compares the binding to __builtin_cpu_is
with the signature declared internally by the GCC back-end and concludes that the cpuname
argument must be a constant pointer-to-string. So, something like this:
function Is_CPU (CPU_Name : access constant String) return Integer;
pragma Import (Intrinsic, Is_CPU, "__builtin_cpu_is");
However, when using this signature, you cannot pass a string literal directly; you must use some indirection:
AMD : aliased constant String := "amd"
and then
Is_CPU (AMD'Access);
This indirection is (as far as I can see) preserved before the GNAT front-end hands over the syntax tree to the GCC back-end; GNAT will not "inline" the string literal (that is: will not remove the indirection; which I guess is actually a good thing as you do not want a constant string to be inlined into function calls in general: multiple functions might reference the string and if the string is very big, the effect of inlining might cause the program size to grow significantly).
On the other hand, if you want to pass a string literal directly in Ada, then you need a signature similar to
function Is_CPU (CPU_Name : String) return Integer;
pragma Import (Intrinsic, Is_CPU, "__builtin_cpu_is");
This signature, however, conflicts with the signature declared by the GCC back-end. Moreover, the GNAT front-end will complain that a string literal cannot be passed-by-copy (something that is likely required for the call to be accepted and recognized by the back-end).
So, I guess some additional logic for handling GCC built-ins with string arguments would have to be added to the GNAT front-end in order for this to work and allow something like this to compile:
function Is_AMD return Boolean is
function Is_CPU (CPU_Name : String) return Integer;
pragma Import (Intrinsic, Is_CPU, "__builtin_cpu_is");
begin
return Is_CPU ("amd") /= 0;
end Is_AMD;
Until then, wrapping the intrinsic with string literal in a separate C function (like the is_amd()
example above) and then importing this C wrapper function in the Ada program will be the way to go.
Solution 2:
Eric found a working solution:
with Ada.Unchecked_Conversion;
with Ada.Text_IO;
with Interfaces.C.Strings;
procedure Main is
procedure CPU_Init;
pragma Import (Intrinsic, CPU_Init, "__builtin_cpu_init");
function Is_CPU (CPU_Name : Interfaces.C.Strings.chars_ptr) return Integer;
pragma Import (Intrinsic, Is_CPU, "__builtin_cpu_is");
function To_Chars_Ptr is
new Ada.Unchecked_Conversion (String, Interfaces.C.Strings.chars_ptr);
begin
CPU_Init;
Ada.Text_IO.Put_Line (Integer'Image (Is_CPU (To_Chars_Ptr ("intel"))));
end;