Bug 1288313 - Ensure the host and target compilers build for the right CPU. r?chmanchester draft
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 22 Apr 2016 15:08:55 +0900
changeset 390438 0bd3978c7decc86748822bdc5a05037aa5ec21d5
parent 390437 ba020a387c27d69f9508c4ae9c8d4555e37b3970
child 390439 877197a941a2fa5e3520ff22b4b3dcbe7dee1058
push id23669
push userbmo:mh+mozilla@glandium.org
push dateThu, 21 Jul 2016 04:54:31 +0000
reviewerschmanchester
bugs1288313
milestone50.0a1
Bug 1288313 - Ensure the host and target compilers build for the right CPU. r?chmanchester And for GCC and clang, try to see if adding -m32, -m64 or --target works if they don't.
build/moz.configure/toolchain.configure
python/mozbuild/mozbuild/configure/constants.py
python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
python/mozbuild/mozbuild/util.py
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -190,16 +190,18 @@ def try_preprocess(compiler, language, s
         os.close(fd)
         cmd = compiler + ['-E', path]
         return check_cmd_output(*cmd)
     finally:
         os.remove(path)
 
 
 @imports(_from='mozbuild.configure.constants', _import='CompilerType')
+@imports(_from='mozbuild.configure.constants',
+         _import='CPU_preprocessor_checks')
 @imports(_from='textwrap', _import='dedent')
 def get_compiler_info(compiler, language):
     '''Returns information about the given `compiler` (command line in the
     form of a list or tuple), in the given `language`.
 
     The returned information includes:
     - the compiler type (msvc, clang-cl, clang or gcc)
     - the compiler version
@@ -244,16 +246,30 @@ def get_compiler_info(compiler, language
         %cplusplus __cplusplus
         #elif __STDC_VERSION__
         %STDC_VERSION __STDC_VERSION__
         #elif __STDC__
         %STDC_VERSION 198900L
         #endif
     ''')
 
+    # While we're doing some preprocessing, we might as well do some more
+    # preprocessor-based tests at the same time, to check the toolchain
+    # matches what we want.
+    for n, (value, condition) in enumerate(CPU_preprocessor_checks.iteritems()):
+        check += dedent('''\
+            #%(if)s %(condition)s
+            %%CPU %(value)s
+        ''' % {
+            'if': 'elif' if n else 'if',
+            'condition': condition,
+            'value': value,
+        })
+    check += '#endif\n'
+
     result = try_preprocess(compiler, language, check)
 
     if not result:
         raise FatalCheckError(
             'Unknown compiler or compiler not supported.')
 
     data = {}
     for line in result.splitlines():
@@ -282,23 +298,24 @@ def get_compiler_info(compiler, language
             version += '.' + msc_ver[4:]
 
     if version:
         version = Version(version)
 
     return namespace(
         type=type,
         version=version,
+        cpu=data.get('CPU'),
         language='C++' if cplusplus else 'C',
         language_version=cplusplus if cplusplus else stdc_version,
     )
 
 
 @imports(_from='mozbuild.shellutil', _import='quote')
-def check_compiler(compiler, language):
+def check_compiler(compiler, language, target):
     info = get_compiler_info(compiler, language)
 
     flags = []
 
     def append_flag(flag):
         if flag not in flags:
             if info.type == 'clang-cl':
                 flags.append('-Xclang')
@@ -328,19 +345,36 @@ def check_compiler(compiler, language):
     # We force clang-cl to emulate Visual C++ 2013 Update 3 with fallback to
     # cl.exe.
     if info.type == 'clang-cl' and info.version != '18.00.30723':
         # Those flags are direct clang-cl flags that don't need -Xclang, add
         # them directly.
         flags.append('-fms-compatibility-version=18.00.30723')
         flags.append('-fallback')
 
+    # Check compiler target
+    # --------------------------------------------------------------------
+    if not info.cpu or info.cpu != target.cpu:
+        if info.type == 'clang':
+            append_flag('--target=%s' % target.toolchain)
+        elif info.type == 'gcc':
+            same_arch_different_bits = (
+                ('x86', 'x86_64'),
+                ('ppc', 'ppc64'),
+                ('sparc', 'sparc64'),
+            )
+            if (target.cpu, info.cpu) in same_arch_different_bits:
+                append_flag('-m32')
+            elif (info.cpu, target.cpu) in same_arch_different_bits:
+                append_flag('-m64')
+
     return namespace(
         type=info.type,
         version=info.version,
+        target_cpu=info.cpu,
         flags=flags,
     )
 
 
 @template
 def default_c_compilers(host_or_target):
     '''Template defining the set of default C compilers for the host and
     target platforms.
@@ -452,38 +486,47 @@ def compiler(language, host_or_target, c
             compiler=without_flags[-1],
             flags=cmd[len(without_flags):],
         )
 
     # Derive the host compiler from the corresponding target compiler when no
     # explicit compiler was given and we're not cross compiling. For the C++
     # compiler, though, prefer to derive from the host C compiler when it
     # doesn't match the target C compiler.
+    # As a special case, since clang supports all kinds of targets in the same
+    # executable, when cross compiling with clang, default to the same compiler
+    # as the target compiler, resetting flags.
     if host_or_target == host:
         args = (c_compiler, other_c_compiler) if other_c_compiler else ()
 
         @depends(provided_compiler, other_compiler, cross_compiling, *args)
         def provided_compiler(value, other_compiler, cross_compiling, *args):
             if value:
                 return value
             c_compiler, other_c_compiler = args if args else (None, None)
             if not cross_compiling and c_compiler == other_c_compiler:
                 return other_compiler
+            if cross_compiling and other_compiler.type == 'clang':
+                return namespace(**{
+                    k: [] if k == 'flags' else v
+                    for k, v in other_compiler.__dict__.iteritems()
+                })
 
     # Normally, we'd use `var` instead of `_var`, but the interaction with
     # old-configure complicates things, and for now, we a) can't take the plain
     # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
     # old-configure AC_SUBST it (because it's autoconf doing it, not us)
     compiler = check_prog('_%s' % var, what=what, progs=default_compilers,
                           input=delayed_getattr(provided_compiler, 'compiler'))
 
-    @depends(compiler, provided_compiler, compiler_wrapper)
+    @depends(compiler, provided_compiler, compiler_wrapper, host_or_target)
     @checking('whether %s can be used' % what, lambda x: bool(x))
     @imports(_from='mozbuild.shellutil', _import='quote')
-    def valid_compiler(compiler, provided_compiler, compiler_wrapper):
+    def valid_compiler(compiler, provided_compiler, compiler_wrapper,
+                       host_or_target):
         wrapper = list(compiler_wrapper or ())
         if provided_compiler:
             provided_wrapper = list(provided_compiler.wrapper)
             # When doing a subconfigure, the compiler is set by old-configure
             # and it contains the wrappers from --with-compiler-wrapper and
             # --with-ccache.
             if provided_wrapper[:len(wrapper)] == wrapper:
                 provided_wrapper = provided_wrapper[len(wrapper):]
@@ -507,23 +550,32 @@ def compiler(language, host_or_target, c
                     % quote(os.path.dirname(full_path)))
             if os.path.normcase(find_program(compiler)) != os.path.normcase(
                     full_path):
                 die('Found `%s` before `%s` in your $PATH. '
                     'Please reorder your $PATH.',
                     quote(os.path.dirname(found_compiler)),
                     quote(os.path.dirname(full_path)))
 
-        info = check_compiler(wrapper + [compiler] + flags, language)
+        info = check_compiler(wrapper + [compiler] + flags, language,
+                              host_or_target)
 
         # Check that the additional flags we got are enough to not require any
         # more flags.
         if info.flags:
             flags += info.flags
-            info = check_compiler(wrapper + [compiler] + flags, language)
+            info = check_compiler(wrapper + [compiler] + flags, language,
+                                  host_or_target)
+
+        if not info.target_cpu or info.target_cpu != host_or_target.cpu:
+            raise FatalCheckError(
+                '%s %s compiler target CPU (%s) does not match --%s CPU (%s)'
+                % (host_or_target_str.capitalize(), language,
+                   info.target_cpu or 'unknown', host_or_target_str,
+                   host_or_target.raw_cpu))
 
         if info.flags:
             raise FatalCheckError(
                 'Unknown compiler or compiler not supported.')
 
         # Compiler version checks
         # ===================================================
         # Check the compiler version here instead of in `compiler_version` so
--- a/python/mozbuild/mozbuild/configure/constants.py
+++ b/python/mozbuild/mozbuild/configure/constants.py
@@ -1,15 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from mozbuild.util import EnumString
+from collections import OrderedDict
 
 
 CompilerType = EnumString.subclass(
     'clang',
     'clang-cl',
     'gcc',
     'msvc',
 )
@@ -54,8 +55,29 @@ CPU = EnumString.subclass(
     'x86',
     'x86_64',
 )
 
 Endianness = EnumString.subclass(
     'big',
     'little',
 )
+
+# The order of those checks matter
+CPU_preprocessor_checks = OrderedDict((
+    ('x86', '__i386__ || _M_IX86'),
+    ('x86_64', '__x86_64__ || _M_X64'),
+    ('arm', '__arm__ || _M_ARM'),
+    ('aarch64', '__aarch64__'),
+    ('ia64', '__ia64__'),
+    ('s390x', '__s390x__'),
+    ('s390', '__s390__'),
+    ('ppc64', '__powerpc64__'),
+    ('ppc', '__powerpc__'),
+    ('Alpha', '__alpha__'),
+    ('hppa', '__hppa__'),
+    ('sparc64', '__sparc__ && __arch64__'),
+    ('sparc', '__sparc__'),
+    ('mips64', '__mips64'),
+    ('mips32', '__mips__'),
+))
+
+assert sorted(CPU_preprocessor_checks.keys()) == sorted(CPU.POSSIBLE_VALUES)
--- a/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
@@ -69,16 +69,40 @@ def GXX(version):
 
 GCC_4_7 = GCC('4.7.3')
 GXX_4_7 = GXX('4.7.3')
 GCC_4_9 = GCC('4.9.3')
 GXX_4_9 = GXX('4.9.3')
 GCC_5 = GCC('5.2.1') + DEFAULT_C11
 GXX_5 = GXX('5.2.1')
 
+GCC_PLATFORM_X86 = {
+    None: {
+        '__i386__': 1,
+    },
+    '-m64': {
+        '__i386__': False,
+        '__x86_64__': 1,
+    },
+}
+
+GCC_PLATFORM_X86_64 = {
+    None: {
+        '__x86_64__': 1,
+    },
+    '-m32': {
+        '__x86_64__': False,
+        '__i386__': 1,
+    },
+}
+
+GCC_PLATFORM_ARM = {
+    '__arm__': 1,
+}
+
 
 @memoize
 def CLANG_BASE(version):
     version = Version(version)
     return FakeCompiler({
         '__clang__': 1,
         '__clang_major__': version.major,
         '__clang_minor__': version.minor,
@@ -102,16 +126,36 @@ CLANGXX_3_3 = CLANGXX('3.3.0')
 CLANG_3_6 = CLANG('3.6.2') + DEFAULT_C11
 CLANGXX_3_6 = CLANGXX('3.6.2') + {
     '-std=gnu++11': {
         '__has_feature(cxx_alignof)': '1',
     },
 }
 
 
+def CLANG_PLATFORM(gcc_platform):
+    base = {
+        '--target=x86_64-linux-gnu': GCC_PLATFORM_X86_64[None],
+        '--target=x86_64-darwin11.2.0': GCC_PLATFORM_X86_64[None],
+        '--target=i686-linux-gnu': GCC_PLATFORM_X86[None],
+        '--target=i686-darwin11.2.0': GCC_PLATFORM_X86[None],
+        '--target=arm-linux-gnu': GCC_PLATFORM_ARM,
+    }
+    undo_gcc_platform = {
+        k: {symbol: False for symbol in gcc_platform[None]}
+        for k in base
+    }
+    return FakeCompiler(gcc_platform, undo_gcc_platform, base)
+
+
+CLANG_PLATFORM_X86 = CLANG_PLATFORM(GCC_PLATFORM_X86)
+
+CLANG_PLATFORM_X86_64 = CLANG_PLATFORM(GCC_PLATFORM_X86_64)
+
+
 @memoize
 def VS(version):
     version = Version(version)
     return FakeCompiler({
         None: {
             '_MSC_VER': '%02d%02d' % (version.major, version.minor),
             '_MSC_FULL_VER': '%02d%02d%05d' % (version.major, version.minor,
                                                version.patch),
@@ -121,27 +165,38 @@ def VS(version):
 
 
 VS_2013u2 = VS('18.00.30501')
 VS_2013u3 = VS('18.00.30723')
 VS_2015 = VS('19.00.23026')
 VS_2015u1 = VS('19.00.23506')
 VS_2015u2 = VS('19.00.23918')
 
+VS_PLATFORM_X86 = {
+    '_M_IX86': 600,
+}
+
+VS_PLATFORM_X86_64 = {
+    '_M_X64': 100,
+}
+
 # Note: In reality, the -std=gnu* options are only supported when preceded by
 # -Xclang.
 CLANG_CL_3_9 = (CLANG_BASE('3.9.0') + VS('18.00.00000') + DEFAULT_C11 +
                 SUPPORTS_GNU99 + SUPPORTS_GNUXX11) + {
     '*.cpp': {
         '__STDC_VERSION__': False,
         '__cplusplus': '201103L',
     },
     '-fms-compatibility-version=18.00.30723': VS('18.00.30723')[None],
 }
 
+CLANG_CL_PLATFORM_X86 = FakeCompiler(VS_PLATFORM_X86, GCC_PLATFORM_X86[None])
+CLANG_CL_PLATFORM_X86_64 = FakeCompiler(VS_PLATFORM_X86_64, GCC_PLATFORM_X86_64[None])
+
 
 class BaseToolchainTest(BaseConfigureTest):
     def setUp(self):
         super(BaseToolchainTest, self).setUp()
         self.out = StringIO()
         self.logger = logging.getLogger('BaseToolchainTest')
         self.logger.setLevel(logging.ERROR)
         self.handler = logging.StreamHandler(self.out)
@@ -192,28 +247,28 @@ class BaseToolchainTest(BaseConfigureTes
             except SystemExit:
                 self.assertEquals((var, result),
                                   (var, self.out.getvalue().strip()))
                 return
 
 
 class LinuxToolchainTest(BaseToolchainTest):
     PATHS = {
-        '/usr/bin/gcc': GCC_4_9,
-        '/usr/bin/g++': GXX_4_9,
-        '/usr/bin/gcc-4.7': GCC_4_7,
-        '/usr/bin/g++-4.7': GXX_4_7,
-        '/usr/bin/gcc-5': GCC_5,
-        '/usr/bin/g++-5': GXX_5,
-        '/usr/bin/clang': CLANG_3_6,
-        '/usr/bin/clang++': CLANGXX_3_6,
-        '/usr/bin/clang-3.6': CLANG_3_6,
-        '/usr/bin/clang++-3.6': CLANGXX_3_6,
-        '/usr/bin/clang-3.3': CLANG_3_3,
-        '/usr/bin/clang++-3.3': CLANGXX_3_3,
+        '/usr/bin/gcc': GCC_4_9 + GCC_PLATFORM_X86_64,
+        '/usr/bin/g++': GXX_4_9 + GCC_PLATFORM_X86_64,
+        '/usr/bin/gcc-4.7': GCC_4_7 + GCC_PLATFORM_X86_64,
+        '/usr/bin/g++-4.7': GXX_4_7 + GCC_PLATFORM_X86_64,
+        '/usr/bin/gcc-5': GCC_5 + GCC_PLATFORM_X86_64,
+        '/usr/bin/g++-5': GXX_5 + GCC_PLATFORM_X86_64,
+        '/usr/bin/clang': CLANG_3_6 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang++': CLANGXX_3_6 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang-3.6': CLANG_3_6 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang++-3.6': CLANGXX_3_6 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang-3.3': CLANG_3_3 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang++-3.3': CLANGXX_3_3 + CLANG_PLATFORM_X86_64,
     }
     GCC_4_7_RESULT = ('Only GCC 4.8 or newer is supported '
                       '(found version 4.7.3).')
     GXX_4_7_RESULT = GCC_4_7_RESULT
     GCC_4_9_RESULT = CompilerResult(
         flags=['-std=gnu99'],
         version='4.9.3',
         type='gcc',
@@ -401,18 +456,18 @@ class LinuxToolchainTest(BaseToolchainTe
         }
         self.do_toolchain_test(paths, {
             'c_compiler': 'Cannot find the target C compiler',
         })
 
     def test_absolute_path(self):
         paths = dict(self.PATHS)
         paths.update({
-            '/opt/clang/bin/clang': CLANG_3_6,
-            '/opt/clang/bin/clang++': CLANGXX_3_6,
+            '/opt/clang/bin/clang': paths['/usr/bin/clang'],
+            '/opt/clang/bin/clang++': paths['/usr/bin/clang++'],
         })
         result = {
             'c_compiler': self.CLANG_3_6_RESULT + {
                 'compiler': '/opt/clang/bin/clang',
             },
             'cxx_compiler': self.CLANGXX_3_6_RESULT + {
                 'compiler': '/opt/clang/bin/clang++'
             },
@@ -424,18 +479,18 @@ class LinuxToolchainTest(BaseToolchainTe
         # With CXX guess too.
         self.do_toolchain_test(paths, result, environ={
             'CC': '/opt/clang/bin/clang',
         })
 
     def test_atypical_name(self):
         paths = dict(self.PATHS)
         paths.update({
-            '/usr/bin/afl-clang-fast': CLANG_3_6,
-            '/usr/bin/afl-clang-fast++': CLANGXX_3_6,
+            '/usr/bin/afl-clang-fast': paths['/usr/bin/clang'],
+            '/usr/bin/afl-clang-fast++': paths['/usr/bin/clang++'],
         })
         self.do_toolchain_test(paths, {
             'c_compiler': self.CLANG_3_6_RESULT + {
                 'compiler': '/usr/bin/afl-clang-fast',
             },
             'cxx_compiler': self.CLANGXX_3_6_RESULT + {
                 'compiler': '/usr/bin/afl-clang-fast++',
             },
@@ -462,16 +517,92 @@ class LinuxToolchainTest(BaseToolchainTe
             'host_cxx_compiler': self.GXX_4_9_RESULT,
         }, environ={
             'CC': 'clang',
             'CXX': 'clang++',
             'HOST_CC': 'gcc',
         })
 
 
+class LinuxSimpleCrossToolchainTest(BaseToolchainTest):
+    TARGET = 'i686-pc-linux-gnu'
+    PATHS = LinuxToolchainTest.PATHS
+    GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
+    GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
+    CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
+    CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
+
+    def test_cross_gcc(self):
+        self.do_toolchain_test(self.PATHS, {
+            'c_compiler': self.GCC_4_9_RESULT + {
+                'flags': ['-m32']
+            },
+            'cxx_compiler': self.GXX_4_9_RESULT + {
+                'flags': ['-m32']
+            },
+            'host_c_compiler': self.GCC_4_9_RESULT,
+            'host_cxx_compiler': self.GXX_4_9_RESULT,
+        })
+
+    def test_cross_clang(self):
+        self.do_toolchain_test(self.PATHS, {
+            'c_compiler': self.CLANG_3_6_RESULT + {
+                'flags': ['--target=i686-linux-gnu'],
+            },
+            'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+                'flags': ['--target=i686-linux-gnu'],
+            },
+            'host_c_compiler': self.CLANG_3_6_RESULT,
+            'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+        }, environ={
+            'CC': 'clang',
+        })
+
+
+class LinuxX86_64CrossToolchainTest(BaseToolchainTest):
+    HOST = 'i686-pc-linux-gnu'
+    TARGET = 'x86_64-pc-linux-gnu'
+    PATHS = {
+        '/usr/bin/gcc': GCC_4_9 + GCC_PLATFORM_X86,
+        '/usr/bin/g++': GXX_4_9 + GCC_PLATFORM_X86,
+        '/usr/bin/clang': CLANG_3_6 + CLANG_PLATFORM_X86,
+        '/usr/bin/clang++': CLANGXX_3_6 + CLANG_PLATFORM_X86,
+    }
+    GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
+    GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
+    CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
+    CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
+
+    def test_cross_gcc(self):
+        self.do_toolchain_test(self.PATHS, {
+            'c_compiler': self.GCC_4_9_RESULT + {
+                'flags': ['-m64']
+            },
+            'cxx_compiler': self.GXX_4_9_RESULT + {
+                'flags': ['-m64']
+            },
+            'host_c_compiler': self.GCC_4_9_RESULT,
+            'host_cxx_compiler': self.GXX_4_9_RESULT,
+        })
+
+    def test_cross_clang(self):
+        self.do_toolchain_test(self.PATHS, {
+            'c_compiler': self.CLANG_3_6_RESULT + {
+                'flags': ['--target=x86_64-linux-gnu'],
+            },
+            'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+                'flags': ['--target=x86_64-linux-gnu'],
+            },
+            'host_c_compiler': self.CLANG_3_6_RESULT,
+            'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+        }, environ={
+            'CC': 'clang',
+        })
+
+
 class OSXToolchainTest(BaseToolchainTest):
     HOST = 'x86_64-apple-darwin11.2.0'
     PATHS = LinuxToolchainTest.PATHS
     CLANG_3_3_RESULT = LinuxToolchainTest.CLANG_3_3_RESULT
     CLANGXX_3_3_RESULT = LinuxToolchainTest.CLANGXX_3_3_RESULT
     CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
     CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
     GCC_4_7_RESULT = LinuxToolchainTest.GCC_4_7_RESULT
@@ -525,24 +656,35 @@ class OSXToolchainTest(BaseToolchainTest
 
 
 class WindowsToolchainTest(BaseToolchainTest):
     HOST = 'i686-pc-mingw32'
 
     # For the purpose of this test, it doesn't matter that the paths are not
     # real Windows paths.
     PATHS = {
-        '/opt/VS_2013u2/bin/cl': VS_2013u2,
-        '/opt/VS_2013u3/bin/cl': VS_2013u3,
-        '/opt/VS_2015/bin/cl': VS_2015,
-        '/opt/VS_2015u1/bin/cl': VS_2015u1,
-        '/usr/bin/cl': VS_2015u2,
-        '/usr/bin/clang-cl': CLANG_CL_3_9,
+        '/opt/VS_2013u2/bin/cl': VS_2013u2 + VS_PLATFORM_X86,
+        '/opt/VS_2013u3/bin/cl': VS_2013u3 + VS_PLATFORM_X86,
+        '/opt/VS_2015/bin/cl': VS_2015 + VS_PLATFORM_X86,
+        '/opt/VS_2015u1/bin/cl': VS_2015u1 + VS_PLATFORM_X86,
+        '/usr/bin/cl': VS_2015u2 + VS_PLATFORM_X86,
+        '/usr/bin/clang-cl': CLANG_CL_3_9 + CLANG_CL_PLATFORM_X86,
+        '/usr/bin/gcc': GCC_4_9 + GCC_PLATFORM_X86,
+        '/usr/bin/g++': GXX_4_9 + GCC_PLATFORM_X86,
+        '/usr/bin/gcc-4.7': GCC_4_7 + GCC_PLATFORM_X86,
+        '/usr/bin/g++-4.7': GXX_4_7 + GCC_PLATFORM_X86,
+        '/usr/bin/gcc-5': GCC_5 + GCC_PLATFORM_X86,
+        '/usr/bin/g++-5': GXX_5 + GCC_PLATFORM_X86,
+        '/usr/bin/clang': CLANG_3_6 + CLANG_PLATFORM_X86,
+        '/usr/bin/clang++': CLANGXX_3_6 + CLANG_PLATFORM_X86,
+        '/usr/bin/clang-3.6': CLANG_3_6 + CLANG_PLATFORM_X86,
+        '/usr/bin/clang++-3.6': CLANGXX_3_6 + CLANG_PLATFORM_X86,
+        '/usr/bin/clang-3.3': CLANG_3_3 + CLANG_PLATFORM_X86,
+        '/usr/bin/clang++-3.3': CLANGXX_3_3 + CLANG_PLATFORM_X86,
     }
-    PATHS.update(LinuxToolchainTest.PATHS)
 
     VS_2013u2_RESULT = (
         'This version (18.00.30501) of the MSVC compiler is not supported.\n'
         'You must install Visual C++ 2015 Update 2 or newer in order to build.\n'
         'See https://developer.mozilla.org/en/Windows_Build_Prerequisites')
     VS_2013u3_RESULT = (
         'This version (18.00.30723) of the MSVC compiler is not supported.\n'
         'You must install Visual C++ 2015 Update 2 or newer in order to build.\n'
@@ -662,51 +804,225 @@ class WindowsToolchainTest(BaseToolchain
         self.do_toolchain_test(self.PATHS, {
             'c_compiler': self.CLANG_3_3_RESULT,
             'cxx_compiler': self.CLANGXX_3_3_RESULT,
         }, environ={
             'CC': 'clang-3.3',
             'CXX': 'clang++-3.3',
         })
 
+    def test_cannot_cross(self):
+        paths = {
+            '/usr/bin/cl': VS_2015u2 + VS_PLATFORM_X86_64,
+        }
+        self.do_toolchain_test(paths, {
+            'c_compiler': ('Target C compiler target CPU (x86_64) '
+                           'does not match --target CPU (i686)'),
+        })
 
-class CrossCompileToolchainTest(BaseToolchainTest):
+
+class Windows64ToolchainTest(WindowsToolchainTest):
+    HOST = 'x86_64-pc-mingw32'
+
+    # For the purpose of this test, it doesn't matter that the paths are not
+    # real Windows paths.
+    PATHS = {
+        '/opt/VS_2013u2/bin/cl': VS_2013u2 + VS_PLATFORM_X86_64,
+        '/opt/VS_2013u3/bin/cl': VS_2013u3 + VS_PLATFORM_X86_64,
+        '/opt/VS_2015/bin/cl': VS_2015 + VS_PLATFORM_X86_64,
+        '/opt/VS_2015u1/bin/cl': VS_2015u1 + VS_PLATFORM_X86_64,
+        '/usr/bin/cl': VS_2015u2 + VS_PLATFORM_X86_64,
+        '/usr/bin/clang-cl': CLANG_CL_3_9 + CLANG_CL_PLATFORM_X86_64,
+        '/usr/bin/gcc': GCC_4_9 + GCC_PLATFORM_X86_64,
+        '/usr/bin/g++': GXX_4_9 + GCC_PLATFORM_X86_64,
+        '/usr/bin/gcc-4.7': GCC_4_7 + GCC_PLATFORM_X86_64,
+        '/usr/bin/g++-4.7': GXX_4_7 + GCC_PLATFORM_X86_64,
+        '/usr/bin/gcc-5': GCC_5 + GCC_PLATFORM_X86_64,
+        '/usr/bin/g++-5': GXX_5 + GCC_PLATFORM_X86_64,
+        '/usr/bin/clang': CLANG_3_6 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang++': CLANGXX_3_6 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang-3.6': CLANG_3_6 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang++-3.6': CLANGXX_3_6 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang-3.3': CLANG_3_3 + CLANG_PLATFORM_X86_64,
+        '/usr/bin/clang++-3.3': CLANGXX_3_3 + CLANG_PLATFORM_X86_64,
+    }
+
+    def test_cannot_cross(self):
+        paths = {
+            '/usr/bin/cl': VS_2015u2 + VS_PLATFORM_X86,
+        }
+        self.do_toolchain_test(paths, {
+            'c_compiler': ('Target C compiler target CPU (x86) '
+                           'does not match --target CPU (x86_64)'),
+        })
+
+
+class LinuxCrossCompileToolchainTest(BaseToolchainTest):
     TARGET = 'arm-unknown-linux-gnu'
     PATHS = {
-        '/usr/bin/arm-linux-gnu-gcc': GCC_4_9,
-        '/usr/bin/arm-linux-gnu-g++': GXX_4_9,
-        '/usr/bin/arm-linux-gnu-gcc-4.7': GCC_4_7,
-        '/usr/bin/arm-linux-gnu-g++-4.7': GXX_4_7,
-        '/usr/bin/arm-linux-gnu-gcc-5': GCC_5,
-        '/usr/bin/arm-linux-gnu-g++-5': GXX_5,
+        '/usr/bin/arm-linux-gnu-gcc': GCC_4_9 + GCC_PLATFORM_ARM,
+        '/usr/bin/arm-linux-gnu-g++': GXX_4_9 + GCC_PLATFORM_ARM,
+        '/usr/bin/arm-linux-gnu-gcc-4.7': GCC_4_7 + GCC_PLATFORM_ARM,
+        '/usr/bin/arm-linux-gnu-g++-4.7': GXX_4_7 + GCC_PLATFORM_ARM,
+        '/usr/bin/arm-linux-gnu-gcc-5': GCC_5 + GCC_PLATFORM_ARM,
+        '/usr/bin/arm-linux-gnu-g++-5': GXX_5 + GCC_PLATFORM_ARM,
     }
     PATHS.update(LinuxToolchainTest.PATHS)
     ARM_GCC_4_7_RESULT = LinuxToolchainTest.GXX_4_7_RESULT
     ARM_GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT + {
         'compiler': '/usr/bin/arm-linux-gnu-gcc-5',
     }
     ARM_GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT + {
         'compiler': '/usr/bin/arm-linux-gnu-g++-5',
     }
     CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
     CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
     GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
     GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
 
-    def test_cross_gcc(self):
-        self.do_toolchain_test(self.PATHS, {
+    PLATFORMS = {
+        'i686-pc-linux-gnu': GCC_PLATFORM_X86,
+        'x86_64-pc-linux-gnu': GCC_PLATFORM_X86_64,
+        'arm-unknown-linux-gnu': GCC_PLATFORM_ARM,
+        'aarch64-unknown-linux-gnu': {
+            '__aarch64__': 1,
+        },
+        'ia64-unknown-linux-gnu': {
+            '__ia64__': 1,
+        },
+        's390x-unknown-linux-gnu': {
+            '__s390x__': 1,
+            '__s390__': 1,
+        },
+        's390-unknown-linux-gnu': {
+            '__s390__': 1,
+        },
+        'powerpc64-unknown-linux-gnu': {
+            None: {
+                '__powerpc64__': 1,
+                '__powerpc__': 1,
+            },
+            '-m32': {
+                '__powerpc64__': False,
+            },
+        },
+        'powerpc-unknown-linux-gnu': {
+            None: {
+                '__powerpc__': 1,
+            },
+            '-m64': {
+                '__powerpc64__': 1,
+            },
+        },
+        'alpha-unknown-linux-gnu': {
+            '__alpha__': 1,
+        },
+        'hppa-unknown-linux-gnu': {
+            '__hppa__': 1,
+        },
+        'sparc64-unknown-linux-gnu': {
+            None: {
+                '__arch64__': 1,
+                '__sparc__': 1,
+            },
+            '-m32': {
+                '__arch64__': False,
+            },
+        },
+        'sparc-unknown-linux-gnu': {
+            None: {
+                '__sparc__': 1,
+            },
+            '-m64': {
+                '__arch64__': 1,
+            },
+        },
+        'mips64-unknown-linux-gnuabi64': {
+            '__mips64': 1,
+            '__mips__': 1,
+        },
+        'mips-unknown-linux-gnu': {
+            '__mips__': 1,
+        },
+    }
+
+    def do_test_cross_gcc_32_64(self, host, target):
+        self.HOST = host
+        self.TARGET = target
+        paths = {
+            '/usr/bin/gcc': GCC_4_9 + self.PLATFORMS[host],
+            '/usr/bin/g++': GXX_4_9 + self.PLATFORMS[host],
+        }
+        cross_flags = {
+            'flags': ['-m64' if '64' in target else '-m32']
+        }
+        self.do_toolchain_test(paths, {
+            'c_compiler': self.GCC_4_9_RESULT + cross_flags,
+            'cxx_compiler': self.GXX_4_9_RESULT + cross_flags,
+            'host_c_compiler': self.GCC_4_9_RESULT,
+            'host_cxx_compiler': self.GXX_4_9_RESULT,
+        })
+        self.HOST = LinuxCrossCompileToolchainTest.HOST
+        self.TARGET = LinuxCrossCompileToolchainTest.TARGET
+
+    def test_cross_x86_x64(self):
+        self.do_test_cross_gcc_32_64(
+            'i686-pc-linux-gnu', 'x86_64-pc-linux-gnu')
+        self.do_test_cross_gcc_32_64(
+            'x86_64-pc-linux-gnu', 'i686-pc-linux-gnu')
+
+    def test_cross_sparc_sparc64(self):
+        self.do_test_cross_gcc_32_64(
+            'sparc-unknown-linux-gnu', 'sparc64-unknown-linux-gnu')
+        self.do_test_cross_gcc_32_64(
+            'sparc64-unknown-linux-gnu', 'sparc-unknown-linux-gnu')
+
+    def test_cross_ppc_ppc64(self):
+        self.do_test_cross_gcc_32_64(
+            'powerpc-unknown-linux-gnu', 'powerpc64-unknown-linux-gnu')
+        self.do_test_cross_gcc_32_64(
+            'powerpc64-unknown-linux-gnu', 'powerpc-unknown-linux-gnu')
+
+    def do_test_cross_gcc(self, host, target):
+        self.HOST = host
+        self.TARGET = target
+        host_cpu = host.split('-')[0]
+        cpu, manufacturer, os = target.split('-', 2)
+        toolchain_prefix = '/usr/bin/%s-%s' % (cpu, os)
+        paths = {
+            '/usr/bin/gcc': GCC_4_9 + self.PLATFORMS[host],
+            '/usr/bin/g++': GXX_4_9 + self.PLATFORMS[host],
+        }
+        self.do_toolchain_test(paths, {
+            'c_compiler': ('Target C compiler target CPU (%s) '
+                           'does not match --target CPU (%s)'
+                           % (host_cpu, cpu)),
+        })
+
+        paths.update({
+            '%s-gcc' % toolchain_prefix: GCC_4_9 + self.PLATFORMS[target],
+            '%s-g++' % toolchain_prefix: GXX_4_9 + self.PLATFORMS[target],
+        })
+        self.do_toolchain_test(paths, {
             'c_compiler': self.GCC_4_9_RESULT + {
-                'compiler': '/usr/bin/arm-linux-gnu-gcc',
+                'compiler': '%s-gcc' % toolchain_prefix,
             },
             'cxx_compiler': self.GXX_4_9_RESULT + {
-                'compiler': '/usr/bin/arm-linux-gnu-g++',
+                'compiler': '%s-g++' % toolchain_prefix,
             },
             'host_c_compiler': self.GCC_4_9_RESULT,
             'host_cxx_compiler': self.GXX_4_9_RESULT,
         })
+        self.HOST = LinuxCrossCompileToolchainTest.HOST
+        self.TARGET = LinuxCrossCompileToolchainTest.TARGET
+
+    def test_cross_gcc_misc(self):
+        for target in self.PLATFORMS:
+            if not target.endswith('-pc-linux-gnu'):
+                self.do_test_cross_gcc('x86_64-pc-linux-gnu', target)
 
     def test_overridden_cross_gcc(self):
         self.do_toolchain_test(self.PATHS, {
             'c_compiler': self.ARM_GCC_5_RESULT,
             'cxx_compiler': self.ARM_GXX_5_RESULT,
             'host_c_compiler': self.GCC_4_9_RESULT,
             'host_cxx_compiler': self.GXX_4_9_RESULT,
         }, environ={
@@ -749,11 +1065,84 @@ class CrossCompileToolchainTest(BaseTool
             'host_c_compiler': self.CLANG_3_6_RESULT,
             'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
         }, environ={
             'CC': 'arm-linux-gnu-gcc-5',
             'CXX': 'arm-linux-gnu-g++-5',
             'HOST_CC': 'clang',
         })
 
+    def test_cross_clang(self):
+        cross_clang_result = self.CLANG_3_6_RESULT + {
+            'flags': ['--target=arm-linux-gnu'],
+        }
+        cross_clangxx_result = self.CLANGXX_3_6_RESULT + {
+            'flags': ['--target=arm-linux-gnu'],
+        }
+        self.do_toolchain_test(self.PATHS, {
+            'c_compiler': cross_clang_result,
+            'cxx_compiler': cross_clangxx_result,
+            'host_c_compiler': self.CLANG_3_6_RESULT,
+            'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+        }, environ={
+            'CC': 'clang',
+            'HOST_CC': 'clang',
+        })
+
+        self.do_toolchain_test(self.PATHS, {
+            'c_compiler': cross_clang_result,
+            'cxx_compiler': cross_clangxx_result,
+            'host_c_compiler': self.CLANG_3_6_RESULT,
+            'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+        }, environ={
+            'CC': 'clang',
+        })
+
+    def test_cross_atypical_clang(self):
+        paths = dict(self.PATHS)
+        paths.update({
+            '/usr/bin/afl-clang-fast': paths['/usr/bin/clang'],
+            '/usr/bin/afl-clang-fast++': paths['/usr/bin/clang++'],
+        })
+        afl_clang_result = self.CLANG_3_6_RESULT + {
+            'compiler': '/usr/bin/afl-clang-fast',
+        }
+        afl_clangxx_result = self.CLANGXX_3_6_RESULT + {
+            'compiler': '/usr/bin/afl-clang-fast++',
+        }
+        self.do_toolchain_test(paths, {
+            'c_compiler': afl_clang_result + {
+                'flags': ['--target=arm-linux-gnu'],
+            },
+            'cxx_compiler': afl_clangxx_result + {
+                'flags': ['--target=arm-linux-gnu'],
+            },
+            'host_c_compiler': afl_clang_result,
+            'host_cxx_compiler': afl_clangxx_result,
+        }, environ={
+            'CC': 'afl-clang-fast',
+            'CXX': 'afl-clang-fast++',
+        })
+
+
+class OSXCrossToolchainTest(BaseToolchainTest):
+    TARGET = 'i686-apple-darwin11.2.0'
+    PATHS = LinuxToolchainTest.PATHS
+    CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
+    CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
+
+    def test_osx_cross(self):
+        self.do_toolchain_test(self.PATHS, {
+            'c_compiler': self.CLANG_3_6_RESULT + {
+                'flags': ['--target=i686-darwin11.2.0'],
+            },
+            'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+                'flags': ['--target=i686-darwin11.2.0'],
+            },
+            'host_c_compiler': self.CLANG_3_6_RESULT,
+            'host_cxx_compiler': self.CLANGXX_3_6_RESULT,
+        }, environ={
+            'CC': 'clang',
+        })
+
 
 if __name__ == '__main__':
     main()
--- a/python/mozbuild/mozbuild/util.py
+++ b/python/mozbuild/mozbuild/util.py
@@ -99,17 +99,18 @@ class ReadOnlyNamespace(object):
 
     def __setattr__(self, key, value):
         raise Exception('Object does not support assignment.')
 
     def __ne__(self, other):
         return not (self == other)
 
     def __eq__(self, other):
-        return self is other or self.__dict__ == other.__dict__
+        return self is other or (
+            hasattr(other, '__dict__') and self.__dict__ == other.__dict__)
 
     def __repr__(self):
         return '<%s %r>' % (self.__class__.__name__, self.__dict__)
 
 
 class ReadOnlyDict(dict):
     """A read-only dictionary."""
     def __init__(self, *args, **kwargs):