Repository: mesonbuild/meson

Issue:
Feature Request: compiler.compute_int()
Need the ability to extract the int values of certain defines that can be determined at compile time, basically a more generic variant of what the compiler.sizeof() check does.

If we're not cross-compiling, the code is pretty much exactly the same.

If we're cross-compiling we'd have to do something slightly different, but it should still be possible. The current code just tries to compile code with values from 1 to 1024 in a loop in such a way that compilation will fail if it's not the value, but it's probably not a good idea to try all values from 0 to MAXINT. It should be possible to bisect though by using code such as

```
#if FOO > 144
#error "larger"
#endif
```

One could check what AC_COMPUTE_INT() does if one can find the actual implementation.

Feature Request: compiler.compute_int()
Need the ability to extract the int values of certain defines that can be determined at compile time, basically a more generic variant of what the compiler.sizeof() check does.

If we're not cross-compiling, the code is pretty much exactly the same.

If we're cross-compiling we'd have to do something slightly different, but it should still be possible. The current code just tries to compile code with values from 1 to 1024 in a loop in such a way that compilation will fail if it's not the value, but it's probably not a good idea to try all values from 0 to MAXINT. It should be possible to bisect though by using code such as

```
#if FOO > 144
#error "larger"
#endif
```

One could check what AC_COMPUTE_INT() does if one can find the actual implementation.



Discussion:
I don't think we need to use the same mechanism as `sizeof()`. For defines we can write the preprocessed output to a file and search for the value in the output.
Maybe it would be nice to have both `compute_int()` and `define_value()`/`macro_value()` ? As `AC_COMPUTE_INT()` isn't restricted to defines.
I don't think we need to use the same mechanism as `sizeof()`. For defines we can write the preprocessed output to a file and search for the value in the output.
Maybe it would be nice to have both `compute_int()` and `define_value()`/`macro_value()` ? As `AC_COMPUTE_INT()` isn't restricted to defines.

Resolving patch:
diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py
--- a/mesonbuild/compilers.py
+++ b/mesonbuild/compilers.py
@@ -998,44 +998,75 @@ def run(self, code, env, extra_args=None, dependencies=None):
         mlog.debug(se)
         return RunResult(True, pe.returncode, so, se)
 
-    def _bisect_compiles(self, t, fargs, env, extra_args, dependencies):
-        # FIXME: Does not actually do bisection right now
-        for i in range(1, 1024):
-            fargs['size'] = i
-            if self.compiles(t.format(**fargs), env, extra_args, dependencies):
-                if self.id == 'msvc':
-                    # MSVC refuses to construct an array of zero size, so
-                    # the test only succeeds when i is sizeof(element) + 1
-                    return i - 1
-                return i
+    def _compile_int(self, expression, prefix, env, extra_args, dependencies):
+        fargs = {'prefix': prefix, 'expression': expression}
+        t = '''#include <stdio.h>
+        {prefix}
+        int main() {{ static int a[1-2*!({expression})]; a[0]=0; return 0; }}'''
+        return self.compiles(t.format(**fargs), env, extra_args, dependencies)
+
+    def cross_compute_int(self, expression, l, h, guess, prefix, env, extra_args, dependencies):
+        if isinstance(guess, int):
+            if self._compile_int('%s == %d' % (expression, guess), prefix, env, extra_args, dependencies):
+                return guess
+
+        cur = l
+        while l < h:
+            cur = int((l + h) / 2)
+            if cur == l:
+                break
+
+            if self._compile_int('%s >= %d' % (expression, cur), prefix, env, extra_args, dependencies):
+                l = cur
+            else:
+                h = cur
+
+        if self._compile_int('%s == %d' % (expression, cur), prefix, env, extra_args, dependencies):
+            return cur
         raise EnvironmentException('Cross-compile check overflowed')
 
-    def cross_sizeof(self, element, prefix, env, extra_args=None, dependencies=None):
+    def compute_int(self, expression, l, h, guess, prefix, env, extra_args=None, dependencies=None):
+        if extra_args is None:
+            extra_args = []
+        if self.is_cross:
+            return self.cross_compute_int(expression, l, h, guess, prefix, env, extra_args, dependencies)
+        fargs = {'prefix': prefix, 'expression': expression}
+        t = '''#include<stdio.h>
+        {prefix}
+        int main(int argc, char **argv) {{
+            printf("%ld\\n", (long)({expression}));
+            return 0;
+        }};'''
+        res = self.run(t.format(**fargs), env, extra_args, dependencies)
+        if not res.compiled:
+            return -1
+        if res.returncode != 0:
+            raise EnvironmentException('Could not run compute_int test binary.')
+        return int(res.stdout)
+
+    def cross_sizeof(self, typename, prefix, env, extra_args=None, dependencies=None):
         if extra_args is None:
             extra_args = []
-        fargs = {'prefix': prefix, 'name': element}
+        fargs = {'prefix': prefix, 'type': typename}
         t = '''#include <stdio.h>
         {prefix}
         int main(int argc, char **argv) {{
-            {name} something;
+            {type} something;
         }}'''
         if not self.compiles(t.format(**fargs), env, extra_args, dependencies):
             return -1
-        t = '''#include <stdio.h>
-        {prefix}
-        int temparray[{size}-sizeof({name})];'''
-        return self._bisect_compiles(t, fargs, env, extra_args, dependencies)
+        return self.cross_compute_int('sizeof(%s)' % typename, 1, 128, None, prefix, env, extra_args, dependencies)
 
-    def sizeof(self, element, prefix, env, extra_args=None, dependencies=None):
+    def sizeof(self, typename, prefix, env, extra_args=None, dependencies=None):
         if extra_args is None:
             extra_args = []
-        fargs = {'prefix': prefix, 'name': element}
+        fargs = {'prefix': prefix, 'type': typename}
         if self.is_cross:
-            return self.cross_sizeof(element, prefix, env, extra_args, dependencies)
+            return self.cross_sizeof(typename, prefix, env, extra_args, dependencies)
         t = '''#include<stdio.h>
         {prefix}
         int main(int argc, char **argv) {{
-            printf("%ld\\n", (long)(sizeof({name})));
+            printf("%ld\\n", (long)(sizeof({type})));
             return 0;
         }};'''
         res = self.run(t.format(**fargs), env, extra_args, dependencies)
@@ -1045,32 +1076,34 @@ def sizeof(self, element, prefix, env, extra_args=None, dependencies=None):
             raise EnvironmentException('Could not run sizeof test binary.')
         return int(res.stdout)
 
-    def cross_alignment(self, typename, env, extra_args=None, dependencies=None):
+    def cross_alignment(self, typename, prefix, env, extra_args=None, dependencies=None):
         if extra_args is None:
             extra_args = []
-        fargs = {'type': typename}
+        fargs = {'prefix': prefix, 'type': typename}
         t = '''#include <stdio.h>
+        {prefix}
         int main(int argc, char **argv) {{
             {type} something;
         }}'''
         if not self.compiles(t.format(**fargs), env, extra_args, dependencies):
             return -1
         t = '''#include <stddef.h>
+        {prefix}
         struct tmp {{
             char c;
             {type} target;
-        }};
-        int testarray[{size}-offsetof(struct tmp, target)];'''
-        return self._bisect_compiles(t, fargs, env, extra_args, dependencies)
+        }};'''
+        return self.cross_compute_int('offsetof(struct tmp, target)', 1, 1024, None, t.format(**fargs), env, extra_args, dependencies)
 
-    def alignment(self, typename, env, extra_args=None, dependencies=None):
+    def alignment(self, typename, prefix, env, extra_args=None, dependencies=None):
         if extra_args is None:
             extra_args = []
         if self.is_cross:
-            return self.cross_alignment(typename, env, extra_args, dependencies)
-        fargs = {'type': typename}
+            return self.cross_alignment(typename, prefix, env, extra_args, dependencies)
+        fargs = {'prefix': prefix, 'type': typename}
         t = '''#include <stdio.h>
         #include <stddef.h>
+        {prefix}
         struct tmp {{
             char c;
             {type} target;
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -631,6 +631,7 @@ def __init__(self, compiler, env):
         self.methods.update({'compiles': self.compiles_method,
                              'links': self.links_method,
                              'get_id': self.get_id_method,
+                             'compute_int': self.compute_int_method,
                              'sizeof': self.sizeof_method,
                              'has_header': self.has_header_method,
                              'has_header_symbol': self.has_header_symbol_method,
@@ -699,8 +700,12 @@ def alignment_method(self, args, kwargs):
             raise InterpreterException('Alignment method takes exactly one positional argument.')
         check_stringlist(args)
         typename = args[0]
+        prefix = kwargs.get('prefix', '')
+        if not isinstance(prefix, str):
+            raise InterpreterException('Prefix argument of sizeof must be a string.')
         extra_args = mesonlib.stringlistify(kwargs.get('args', []))
-        result = self.compiler.alignment(typename, self.environment, extra_args)
+        deps = self.determine_dependencies(kwargs)
+        result = self.compiler.alignment(typename, prefix, self.environment, extra_args, deps)
         mlog.log('Checking for alignment of "', mlog.bold(typename), '": ', result, sep='')
         return result
 
@@ -822,6 +827,29 @@ def has_type_method(self, args, kwargs):
         mlog.log('Checking for type "', mlog.bold(typename), '": ', hadtxt, sep='')
         return had
 
+    def compute_int_method(self, args, kwargs):
+        if len(args) != 1:
+            raise InterpreterException('Compute_int takes exactly one argument.')
+        check_stringlist(args)
+        expression = args[0]
+        prefix = kwargs.get('prefix', '')
+        l = kwargs.get('low', -1024)
+        h = kwargs.get('high', 1024)
+        guess = kwargs.get('guess', None)
+        if not isinstance(prefix, str):
+            raise InterpreterException('Prefix argument of compute_int must be a string.')
+        if not isinstance(l, int):
+            raise InterpreterException('Low argument of compute_int must be an int.')
+        if not isinstance(h, int):
+            raise InterpreterException('High argument of compute_int must be an int.')
+        if guess is not None and not isinstance(guess, int):
+            raise InterpreterException('Guess argument of compute_int must be an int.')
+        extra_args = self.determine_args(kwargs)
+        deps = self.determine_dependencies(kwargs)
+        res = self.compiler.compute_int(expression, l, h, guess, prefix, self.environment, extra_args, deps)
+        mlog.log('Computing int of "%s": %d' % (expression, res))
+        return res
+
     def sizeof_method(self, args, kwargs):
         if len(args) != 1:
             raise InterpreterException('Sizeof takes exactly one argument.')


Test patch:
diff --git a/test cases/common/139 compute int/config.h.in b/test cases/common/139 compute int/config.h.in
new file mode 100644
--- /dev/null
+++ b/test cases/common/139 compute int/config.h.in	
@@ -0,0 +1,2 @@
+#define INTSIZE @INTSIZE@
+#define FOOBAR_IN_CONFIG_H @FOOBAR@
diff --git a/test cases/common/139 compute int/foobar.h b/test cases/common/139 compute int/foobar.h
new file mode 100644
--- /dev/null
+++ b/test cases/common/139 compute int/foobar.h	
@@ -0,0 +1,6 @@
+#ifndef __FOOBAR_H__
+#define __FOOBAR_H__
+
+#define FOOBAR_IN_FOOBAR_H    10
+
+#endif /*__FOOBAR_H__*/
diff --git a/test cases/common/139 compute int/meson.build b/test cases/common/139 compute int/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/common/139 compute int/meson.build	
@@ -0,0 +1,35 @@
+project('compute int', 'c', 'cpp')
+
+inc = include_directories('.')
+
+# Test with C
+cc = meson.get_compiler('c')
+
+intsize = cc.compute_int('sizeof(int)', low : 1, high : 16, guess : 4)
+foobar = cc.compute_int('FOOBAR_IN_FOOBAR_H', prefix : '#include "foobar.h"', include_directories : inc)
+
+cd = configuration_data()
+cd.set('INTSIZE', intsize)
+cd.set('FOOBAR', foobar)
+cd.set('CONFIG', 'config.h')
+configure_file(input : 'config.h.in', output : 'config.h', configuration : cd)
+s = configure_file(input : 'prog.c.in', output : 'prog.c', configuration : cd)
+
+e = executable('prog', s)
+test('compute int test', e)
+
+# Test with C++
+cpp = meson.get_compiler('cpp')
+
+intsize = cpp.compute_int('sizeof(int)')
+foobar = cpp.compute_int('FOOBAR_IN_FOOBAR_H', prefix : '#include "foobar.h"', include_directories : inc)
+
+cdpp = configuration_data()
+cdpp.set('INTSIZE', intsize)
+cdpp.set('FOOBAR', foobar)
+cdpp.set('CONFIG', 'config.hpp')
+configure_file(input : 'config.h.in', output : 'config.hpp', configuration : cdpp)
+spp = configure_file(input : 'prog.c.in', output : 'prog.cc', configuration : cdpp)
+
+epp = executable('progpp', spp)
+test('compute int test c++', epp)
diff --git a/test cases/common/139 compute int/prog.c.in b/test cases/common/139 compute int/prog.c.in
new file mode 100644
--- /dev/null
+++ b/test cases/common/139 compute int/prog.c.in	
@@ -0,0 +1,16 @@
+#include "@CONFIG@"
+#include <stdio.h>
+#include <wchar.h>
+#include "foobar.h"
+
+int main(int argc, char **argv) {
+    if(INTSIZE != sizeof(int)) {
+        fprintf(stderr, "Mismatch: computed int size %d, actual size %d.\n", INTSIZE, (int)sizeof(int));
+        return 1;
+    }
+    if(FOOBAR_IN_CONFIG_H != FOOBAR_IN_FOOBAR_H) {
+        fprintf(stderr, "Mismatch: computed int %d, should be %d.\n", FOOBAR_IN_CONFIG_H, FOOBAR_IN_FOOBAR_H);
+        return 1;
+    }
+    return 0;
+}