Bug 1288313 - Ensure the host and target compilers build for the right CPU. r=chmanchester
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 22 Apr 2016 15:08:55 +0900
changeset 306275 f0889768e16d8713c11ad08579785d4a178d4309
parent 306274 d4d67c4aa318365a11642b24c53838da0a9b089c
child 306276 ba8cf05d0ee715aabb7b788f2026819a634a50f1
push id30783
push usermh@glandium.org
push dateFri, 22 Jul 2016 21:50:31 +0000
treeherderautoland@51fe63c13c4c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschmanchester
bugs1288313
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -240,16 +242,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():
@@ -278,23 +294,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')
@@ -324,19 +341,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.
@@ -448,38 +482,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):]
@@ -503,23 +546,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):