Add Om1 lowering with no optimizations.
This adds infrastructure for low-level x86-32 instructions, and the target lowering patterns.
Practically no optimizations are performed. Optimizations to be introduced later include liveness analysis, dead-code elimination, global linear-scan register allocation, linear-scan based stack slot coalescing, and compare/branch fusing. One optimization that is present is simple coalescing of stack slots for variables that are only live within a single basic block.
There are also some fairly comprehensive cross tests. This testing infrastructure translates bitcode using both Subzero and llc, and a testing harness calls both versions with a variety of "interesting" inputs and compares the results. Specifically, Arithmetic, Icmp, Fcmp, and Cast instructions are tested this way, across all PNaCl primitive types.
BUG=
R=jvoung@chromium.org
Review URL: https://codereview.chromium.org/265703002
diff --git a/crosstest/crosstest.py b/crosstest/crosstest.py
new file mode 100755
index 0000000..e835193
--- /dev/null
+++ b/crosstest/crosstest.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python2
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+import tempfile
+
+sys.path.insert(0, '../pydir')
+from utils import shellcmd
+
+if __name__ == '__main__':
+ """Builds a cross-test binary that allows functions translated by
+ Subzero and llc to be compared.
+
+ Each --test argument is compiled once by llc and once by Subzero.
+ C/C++ tests are first compiled down to PNaCl bitcode by the
+ build-pnacl-ir.py script. The --prefix argument ensures that
+ symbol names are different between the two object files, to avoid
+ linking errors.
+
+ There is also a --driver argument that specifies the C/C++ file
+ that calls the test functions with a variety of interesting inputs
+ and compares their results.
+ """
+ # arch_map maps a Subzero target string to an llvm-mc -arch string.
+ arch_map = { 'x8632':'x86', 'x8664':'x86-64', 'arm':'arm' }
+ desc = 'Build a cross-test that compares Subzero and llc translation.'
+ argparser = argparse.ArgumentParser(description=desc)
+ argparser.add_argument('--test', required=True, action='append',
+ metavar='TESTFILE_LIST',
+ help='List of C/C++/.ll files with test functions')
+ argparser.add_argument('--driver', required=True,
+ metavar='DRIVER',
+ help='Driver program')
+ argparser.add_argument('--target', required=False, default='x8632',
+ choices=arch_map.keys(),
+ metavar='TARGET',
+ help='Translation target architecture')
+ argparser.add_argument('-O', required=False, default='2', dest='optlevel',
+ choices=['m1', '-1', '0', '1', '2'],
+ metavar='OPTLEVEL',
+ help='Optimization level ' +
+ '(m1 and -1 are equivalent)')
+ argparser.add_argument('--prefix', required=True,
+ metavar='SZ_PREFIX',
+ help='String prepended to Subzero symbol names')
+ argparser.add_argument('--output', '-o', required=True,
+ metavar='EXECUTABLE',
+ help='Executable to produce')
+ argparser.add_argument('--dir', required=False, default='.',
+ metavar='OUTPUT_DIR',
+ help='Output directory for all files')
+ argparser.add_argument('--llvm-bin-path', required=False,
+ default=os.environ.get('LLVM_BIN_PATH'),
+ metavar='PATH',
+ help='Path to LLVM executables like llc ' +
+ '(defaults to $LLVM_BIN_PATH)')
+ args = argparser.parse_args()
+
+ objs = []
+ remove_internal = re.compile('^define internal ')
+ fix_target = re.compile('le32-unknown-nacl')
+ llvm_bin_path = args.llvm_bin_path
+ for arg in args.test:
+ base, ext = os.path.splitext(arg)
+ if ext == '.ll':
+ bitcode = arg
+ else:
+ bitcode = os.path.join(args.dir, base + '.pnacl.ll')
+ shellcmd(['../pydir/build-pnacl-ir.py', '--disable-verify',
+ '--dir', args.dir, arg])
+ # Read in the bitcode file, fix it up, and rewrite the file.
+ f = open(bitcode)
+ ll_lines = f.readlines()
+ f.close()
+ f = open(bitcode, 'w')
+ for line in ll_lines:
+ line = remove_internal.sub('define ', line)
+ line = fix_target.sub('i686-pc-linux-gnu', line)
+ f.write(line)
+ f.close()
+
+ asm_sz = os.path.join(args.dir, base + '.sz.s')
+ obj_sz = os.path.join(args.dir, base + '.sz.o')
+ obj_llc = os.path.join(args.dir, base + '.llc.o')
+ shellcmd(['../llvm2ice',
+ '-O' + args.optlevel,
+ '--target=' + args.target,
+ '--prefix=' + args.prefix,
+ '-o=' + asm_sz,
+ bitcode])
+ shellcmd([os.path.join(llvm_bin_path, 'llvm-mc'),
+ '-arch=' + arch_map[args.target],
+ '-x86-asm-syntax=intel',
+ '-filetype=obj',
+ '-o=' + obj_sz,
+ asm_sz])
+ objs.append(obj_sz)
+ # Each original bitcode file needs to be translated by the
+ # LLVM toolchain and have its object file linked in. There
+ # are two ways to do this: explicitly use llc, or include the
+ # .ll file in the link command. It turns out that these two
+ # approaches can produce different semantics on some undefined
+ # bitcode behavior. Specifically, LLVM produces different
+ # results for overflowing fptoui instructions for i32 and i64
+ # on x86-32. As it turns out, Subzero lowering was based on
+ # inspecting the object code produced by the direct llc
+ # command, so we need to directly run llc on the bitcode, even
+ # though it makes this script longer, to avoid spurious
+ # failures. This behavior can be inspected by switching
+ # use_llc between True and False.
+ use_llc = False
+ if use_llc:
+ shellcmd([os.path.join(llvm_bin_path, 'llc'),
+ '-filetype=obj',
+ '-o=' + obj_llc,
+ bitcode])
+ objs.append(obj_llc)
+ else:
+ objs.append(bitcode)
+
+ linker = 'clang' if os.path.splitext(args.driver)[1] == '.c' else 'clang++'
+ shellcmd([os.path.join(llvm_bin_path, linker), '-g', '-m32', args.driver] +
+ objs +
+ ['-lm', '-o', os.path.join(args.dir, args.output)])
diff --git a/crosstest/runtests.sh b/crosstest/runtests.sh
new file mode 100755
index 0000000..400e7a4
--- /dev/null
+++ b/crosstest/runtests.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+# TODO: Retire this script and move the individual tests into the lit
+# framework, to leverage parallel testing and other lit goodness.
+
+set -eux
+
+OPTLEVELS="m1"
+OUTDIR=Output
+# Clean the output directory to avoid reusing stale results.
+rm -rf "${OUTDIR}"
+mkdir -p "${OUTDIR}"
+
+for optlevel in ${OPTLEVELS} ; do
+
+ ./crosstest.py -O${optlevel} --prefix=Subzero_ --target=x8632 \
+ --dir="${OUTDIR}" \
+ --llvm-bin-path="${LLVM_BIN_PATH}" \
+ --test=simple_loop.c \
+ --driver=simple_loop_main.c \
+ --output=simple_loop_O${optlevel}
+
+ ./crosstest.py -O${optlevel} --prefix=Subzero_ --target=x8632 \
+ --dir="${OUTDIR}" \
+ --llvm-bin-path="${LLVM_BIN_PATH}" \
+ --test=test_cast.cpp --test=test_cast_to_u1.ll \
+ --driver=test_cast_main.cpp \
+ --output=test_cast_O${optlevel}
+
+ ./crosstest.py -O${optlevel} --prefix=Subzero_ --target=x8632 \
+ --dir="${OUTDIR}" \
+ --llvm-bin-path="${LLVM_BIN_PATH}" \
+ --test=test_fcmp.pnacl.ll \
+ --driver=test_fcmp_main.cpp \
+ --output=test_fcmp_O${optlevel}
+
+ ./crosstest.py -O${optlevel} --prefix=Subzero_ --target=x8632 \
+ --dir="${OUTDIR}" \
+ --llvm-bin-path="${LLVM_BIN_PATH}" \
+ --test=test_icmp.cpp \
+ --driver=test_icmp_main.cpp \
+ --output=test_icmp_O${optlevel}
+
+ ./crosstest.py -O${optlevel} --prefix=Subzero_ --target=x8632 \
+ --dir="${OUTDIR}" \
+ --llvm-bin-path="${LLVM_BIN_PATH}" \
+ --test=test_arith.cpp --test=test_arith_frem.ll \
+ --driver=test_arith_main.cpp \
+ --output=test_arith_O${optlevel}
+
+done
+
+for optlevel in ${OPTLEVELS} ; do
+ "${OUTDIR}"/simple_loop_O${optlevel}
+ "${OUTDIR}"/test_cast_O${optlevel}
+ "${OUTDIR}"/test_fcmp_O${optlevel}
+ "${OUTDIR}"/test_icmp_O${optlevel}
+ "${OUTDIR}"/test_arith_O${optlevel}
+done
diff --git a/crosstest/simple_loop.c b/crosstest/simple_loop.c
new file mode 100644
index 0000000..6568380
--- /dev/null
+++ b/crosstest/simple_loop.c
@@ -0,0 +1,10 @@
+// This is a simple loop that sums elements of an input array and
+// returns the result. It's here mainly because it's one of the
+// simple examples guiding the early Subzero design.
+
+int simple_loop(int *a, int n) {
+ int sum = 0;
+ for (int i = 0; i < n; ++i)
+ sum += a[i];
+ return sum;
+}
diff --git a/crosstest/simple_loop_main.c b/crosstest/simple_loop_main.c
new file mode 100644
index 0000000..5ff36b8
--- /dev/null
+++ b/crosstest/simple_loop_main.c
@@ -0,0 +1,29 @@
+/* crosstest.py --test=simple_loop.c --driver=simple_loop_main.c \
+ --prefix=Subzero_ --output=simple_loop */
+
+#include <stdio.h>
+
+int simple_loop(int *a, int n);
+int Subzero_simple_loop(int *a, int n);
+
+int main(int argc, char **argv) {
+ unsigned TotalTests = 0;
+ unsigned Passes = 0;
+ unsigned Failures = 0;
+ int a[100];
+ for (int i = 0; i < 100; ++i)
+ a[i] = i * 2 - 100;
+ for (int i = -2; i < 100; ++i) {
+ ++TotalTests;
+ int llc_result = simple_loop(a, i);
+ int sz_result = Subzero_simple_loop(a, i);
+ if (llc_result == sz_result) {
+ ++Passes;
+ } else {
+ ++Failures;
+ printf("Failure: i=%d, llc=%d, sz=%d\n", i, llc_result, sz_result);
+ }
+ }
+ printf("TotalTests=%u Passes=%u Failures=%u\n", TotalTests, Passes, Failures);
+ return Failures;
+}
diff --git a/crosstest/test_arith.cpp b/crosstest/test_arith.cpp
new file mode 100644
index 0000000..18b4b57
--- /dev/null
+++ b/crosstest/test_arith.cpp
@@ -0,0 +1,30 @@
+// This aims to test all the arithmetic bitcode instructions across
+// all PNaCl primitive data types.
+
+#include <stdint.h>
+
+#include "test_arith.h"
+
+#define X(inst, op, isdiv) \
+ bool test##inst(bool a, bool b) { return a op b; } \
+ uint8_t test##inst(uint8_t a, uint8_t b) { return a op b; } \
+ uint16_t test##inst(uint16_t a, uint16_t b) { return a op b; } \
+ uint32_t test##inst(uint32_t a, uint32_t b) { return a op b; } \
+ uint64_t test##inst(uint64_t a, uint64_t b) { return a op b; }
+UINTOP_TABLE
+#undef X
+
+#define X(inst, op, isdiv) \
+ bool test##inst(bool a, bool b) { return a op b; } \
+ int8_t test##inst(int8_t a, int8_t b) { return a op b; } \
+ int16_t test##inst(int16_t a, int16_t b) { return a op b; } \
+ int32_t test##inst(int32_t a, int32_t b) { return a op b; } \
+ int64_t test##inst(int64_t a, int64_t b) { return a op b; }
+SINTOP_TABLE
+#undef X
+
+#define X(inst, op, func) \
+ float test##inst(float a, float b) { return func(a op b); } \
+ double test##inst(double a, double b) { return func(a op b); }
+FPOP_TABLE
+#undef X
diff --git a/crosstest/test_arith.def b/crosstest/test_arith.def
new file mode 100644
index 0000000..4cf4596
--- /dev/null
+++ b/crosstest/test_arith.def
@@ -0,0 +1,45 @@
+#ifndef TEST_ARITH_DEF
+#define TEST_ARITH_DEF
+
+#define XSTR(s) STR(s)
+#define STR(s) #s
+
+#define UINTOP_TABLE \
+ /* inst, operator, div */ \
+ X(Add, +, 0 ) \
+ X(Sub, -, 0 ) \
+ X(Mul, *, 0 ) \
+ X(Udiv, /, 1 ) \
+ X(Urem, %, 1 ) \
+ X(Shl, <<, 0) \
+ X(Lshr, >>, 0) \
+ X(And, &, 0 ) \
+ X(Or, |, 0 ) \
+ X(Xor, ^, 0 ) \
+//#define X(inst, op, isdiv)
+
+#define SINTOP_TABLE \
+ /* inst, operator, div */ \
+ X(Sdiv, /, 1) \
+ X(Srem, %, 1) \
+ X(Ashr, >>, 0) \
+//#define X(inst, op, isdiv)
+
+#define COMMA ,
+#define FPOP_TABLE \
+ /* inst, infix_op, func */ \
+ X(Fadd, +, ) \
+ X(Fsub, -, ) \
+ X(Fmul, *, ) \
+ X(Fdiv, /, ) \
+ X(Frem, COMMA, myFrem) \
+//#define X(inst, op, func)
+
+// Note: The above definition of COMMA, plus the "func" argument to
+// the X macro, are because C++ does not allow the % operator on
+// floating-point primitive types. To work around this, the expansion
+// is "func(a infix_op b)", which becomes "myFrem(a , b)" for the Frem
+// instruction and "(a + b)" for the Fadd instruction. The two
+// versions of myFrem() are defined in a separate bitcode file.
+
+#endif // TEST_ARITH_DEF
diff --git a/crosstest/test_arith.h b/crosstest/test_arith.h
new file mode 100644
index 0000000..996d962
--- /dev/null
+++ b/crosstest/test_arith.h
@@ -0,0 +1,29 @@
+#include <stdint.h>
+#include "test_arith.def"
+
+#define X(inst, op, isdiv) \
+ bool test##inst(bool a, bool b); \
+ uint8_t test##inst(uint8_t a, uint8_t b); \
+ uint16_t test##inst(uint16_t a, uint16_t b); \
+ uint32_t test##inst(uint32_t a, uint32_t b); \
+ uint64_t test##inst(uint64_t a, uint64_t b);
+UINTOP_TABLE
+#undef X
+
+#define X(inst, op, isdiv) \
+ bool test##inst(bool a, bool b); \
+ int8_t test##inst(int8_t a, int8_t b); \
+ int16_t test##inst(int16_t a, int16_t b); \
+ int32_t test##inst(int32_t a, int32_t b); \
+ int64_t test##inst(int64_t a, int64_t b);
+SINTOP_TABLE
+#undef X
+
+float myFrem(float a, float b);
+double myFrem(double a, double b);
+
+#define X(inst, op, func) \
+ float test##inst(float a, float b); \
+ double test##inst(double a, double b);
+FPOP_TABLE
+#undef X
diff --git a/crosstest/test_arith_frem.ll b/crosstest/test_arith_frem.ll
new file mode 100644
index 0000000..34b7156
--- /dev/null
+++ b/crosstest/test_arith_frem.ll
@@ -0,0 +1,11 @@
+target triple = "i686-pc-linux-gnu"
+
+define float @_Z6myFremff(float %a, float %b) {
+ %rem = frem float %a, %b
+ ret float %rem
+}
+
+define double @_Z6myFremdd(double %a, double %b) {
+ %rem = frem double %a, %b
+ ret double %rem
+}
diff --git a/crosstest/test_arith_main.cpp b/crosstest/test_arith_main.cpp
new file mode 100644
index 0000000..745da61
--- /dev/null
+++ b/crosstest/test_arith_main.cpp
@@ -0,0 +1,193 @@
+/* crosstest.py --test=test_arith.cpp --test=test_arith_frem.ll \
+ --driver=test_arith_main.cpp --prefix=Subzero_ --output=test_arith */
+
+#include <stdint.h>
+
+#include <cfloat>
+#include <cstring> // memcmp
+#include <iostream>
+
+// Include test_arith.h twice - once normally, and once within the
+// Subzero_ namespace, corresponding to the llc and Subzero translated
+// object files, respectively.
+#include "test_arith.h"
+namespace Subzero_ {
+#include "test_arith.h"
+}
+
+volatile unsigned Values[] = { 0x0, 0x1, 0x7ffffffe, 0x7fffffff,
+ 0x80000000, 0x80000001, 0xfffffffe, 0xffffffff,
+ 0x7e, 0x7f, 0x80, 0x81,
+ 0xfe, 0xff, 0x100, 0x101,
+ 0x7ffe, 0x7fff, 0x8000, 0x8001,
+ 0xfffe, 0xffff, 0x10000, 0x10001, };
+const static size_t NumValues = sizeof(Values) / sizeof(*Values);
+
+template <typename TypeUnsigned, typename TypeSigned>
+void testsInt(size_t &TotalTests, size_t &Passes, size_t &Failures) {
+ typedef TypeUnsigned (*FuncTypeUnsigned)(TypeUnsigned, TypeUnsigned);
+ typedef TypeSigned (*FuncTypeSigned)(TypeSigned, TypeSigned);
+ static struct {
+ const char *Name;
+ FuncTypeUnsigned FuncLlc;
+ FuncTypeUnsigned FuncSz;
+ bool ExcludeDivExceptions; // for divide related tests
+ } Funcs[] = {
+#define X(inst, op, isdiv) \
+ { \
+ STR(inst), (FuncTypeUnsigned)test##inst, \
+ (FuncTypeUnsigned)Subzero_::test##inst, isdiv \
+ } \
+ ,
+ UINTOP_TABLE
+#undef X
+#define X(inst, op, isdiv) \
+ { \
+ STR(inst), (FuncTypeUnsigned)(FuncTypeSigned)test##inst, \
+ (FuncTypeUnsigned)(FuncTypeSigned)Subzero_::test##inst, isdiv \
+ } \
+ ,
+ SINTOP_TABLE
+#undef X
+ };
+ const static size_t NumFuncs = sizeof(Funcs) / sizeof(*Funcs);
+
+ if (sizeof(TypeUnsigned) <= sizeof(uint32_t)) {
+ // This is the "normal" version of the loop nest, for 32-bit or
+ // narrower types.
+ for (size_t f = 0; f < NumFuncs; ++f) {
+ for (size_t i = 0; i < NumValues; ++i) {
+ for (size_t j = 0; j < NumValues; ++j) {
+ TypeUnsigned Value1 = Values[i];
+ TypeUnsigned Value2 = Values[j];
+ // Avoid HW divide-by-zero exception.
+ if (Funcs[f].ExcludeDivExceptions && Value2 == 0)
+ continue;
+ // Avoid HW overflow exception (on x86-32). TODO: adjust
+ // for other architectures.
+ if (Funcs[f].ExcludeDivExceptions && Value1 == 0x80000000 &&
+ Value2 == 0xffffffff)
+ continue;
+ ++TotalTests;
+ TypeUnsigned ResultSz = Funcs[f].FuncSz(Value1, Value2);
+ TypeUnsigned ResultLlc = Funcs[f].FuncLlc(Value1, Value2);
+ if (ResultSz == ResultLlc) {
+ ++Passes;
+ } else {
+ ++Failures;
+ std::cout << "test" << Funcs[f].Name << (8 * sizeof(TypeUnsigned))
+ << "(" << Value1 << ", " << Value2
+ << "): sz=" << (unsigned)ResultSz
+ << " llc=" << (unsigned)ResultLlc << std::endl;
+ }
+ }
+ }
+ }
+ } else {
+ // This is the 64-bit version. Test values are synthesized from
+ // the 32-bit values in Values[].
+ for (size_t f = 0; f < NumFuncs; ++f) {
+ for (size_t iLo = 0; iLo < NumValues; ++iLo) {
+ for (size_t iHi = 0; iHi < NumValues; ++iHi) {
+ for (size_t jLo = 0; jLo < NumValues; ++jLo) {
+ for (size_t jHi = 0; jHi < NumValues; ++jHi) {
+ TypeUnsigned Value1 =
+ (((TypeUnsigned)Values[iHi]) << 32) + Values[iLo];
+ TypeUnsigned Value2 =
+ (((TypeUnsigned)Values[jHi]) << 32) + Values[jLo];
+ // Avoid HW divide-by-zero exception.
+ if (Funcs[f].ExcludeDivExceptions && Value2 == 0)
+ continue;
+ ++TotalTests;
+ TypeUnsigned ResultSz = Funcs[f].FuncSz(Value1, Value2);
+ TypeUnsigned ResultLlc = Funcs[f].FuncLlc(Value1, Value2);
+ if (ResultSz == ResultLlc) {
+ ++Passes;
+ } else {
+ ++Failures;
+ std::cout << "test" << Funcs[f].Name
+ << (8 * sizeof(TypeUnsigned)) << "(" << Value1 << ", "
+ << Value2 << "): sz=" << (unsigned)ResultSz
+ << " llc=" << (unsigned)ResultLlc << std::endl;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+template <typename Type>
+void testsFp(size_t &TotalTests, size_t &Passes, size_t &Failures) {
+ static const Type NegInf = -1.0 / 0.0;
+ static const Type PosInf = 1.0 / 0.0;
+ static const Type Nan = 0.0 / 0.0;
+ volatile Type Values[] = {
+ 0, 1, 0x7e,
+ 0x7f, 0x80, 0x81,
+ 0xfe, 0xff, 0x7ffe,
+ 0x7fff, 0x8000, 0x8001,
+ 0xfffe, 0xffff, 0x7ffffffe,
+ 0x7fffffff, 0x80000000, 0x80000001,
+ 0xfffffffe, 0xffffffff, 0x100000000ll,
+ 0x100000001ll, 0x7ffffffffffffffell, 0x7fffffffffffffffll,
+ 0x8000000000000000ll, 0x8000000000000001ll, 0xfffffffffffffffell,
+ 0xffffffffffffffffll, NegInf, PosInf,
+ Nan, FLT_MIN, FLT_MAX,
+ DBL_MIN, DBL_MAX
+ };
+ const static size_t NumValues = sizeof(Values) / sizeof(*Values);
+ typedef Type (*FuncType)(Type, Type);
+ static struct {
+ const char *Name;
+ FuncType FuncLlc;
+ FuncType FuncSz;
+ } Funcs[] = {
+#define X(inst, op, func) \
+ { STR(inst), (FuncType)test##inst, (FuncType)Subzero_::test##inst } \
+ ,
+ FPOP_TABLE
+#undef X
+ };
+ const static size_t NumFuncs = sizeof(Funcs) / sizeof(*Funcs);
+
+ for (size_t f = 0; f < NumFuncs; ++f) {
+ for (size_t i = 0; i < NumValues; ++i) {
+ for (size_t j = 0; j < NumValues; ++j) {
+ Type Value1 = Values[i];
+ Type Value2 = Values[j];
+ ++TotalTests;
+ Type ResultSz = Funcs[f].FuncSz(Value1, Value2);
+ Type ResultLlc = Funcs[f].FuncLlc(Value1, Value2);
+ // Compare results using memcmp() in case they are both NaN.
+ if (!memcmp(&ResultSz, &ResultLlc, sizeof(Type))) {
+ ++Passes;
+ } else {
+ ++Failures;
+ std::cout << std::fixed << "test" << Funcs[f].Name
+ << (8 * sizeof(Type)) << "(" << Value1 << ", " << Value2
+ << "): sz=" << ResultSz << " llc=" << ResultLlc
+ << std::endl;
+ }
+ }
+ }
+ }
+}
+
+int main(int argc, char **argv) {
+ size_t TotalTests = 0;
+ size_t Passes = 0;
+ size_t Failures = 0;
+
+ testsInt<uint8_t, int8_t>(TotalTests, Passes, Failures);
+ testsInt<uint16_t, int16_t>(TotalTests, Passes, Failures);
+ testsInt<uint32_t, int32_t>(TotalTests, Passes, Failures);
+ testsInt<uint64_t, int64_t>(TotalTests, Passes, Failures);
+ testsFp<float>(TotalTests, Passes, Failures);
+ testsFp<double>(TotalTests, Passes, Failures);
+
+ std::cout << "TotalTests=" << TotalTests << " Passes=" << Passes
+ << " Failures=" << Failures << "\n";
+ return Failures;
+}
diff --git a/crosstest/test_cast.cpp b/crosstest/test_cast.cpp
new file mode 100644
index 0000000..1035109
--- /dev/null
+++ b/crosstest/test_cast.cpp
@@ -0,0 +1,61 @@
+// This aims to test all the conversion bitcode instructions across
+// all PNaCl primitive data types.
+
+#include <stdint.h>
+#include "test_cast.h"
+
+template <typename FromType, typename ToType>
+ToType __attribute__((noinline)) cast(FromType a) {
+ return (ToType)a;
+}
+
+template <typename FromType, typename ToType>
+ToType __attribute__((noinline)) castBits(FromType a) {
+ return *(ToType *)&a;
+}
+
+// The purpose of the following sets of templates is to force
+// cast<A,B>() to be instantiated in the resulting bitcode file for
+// all <A,B>, so that they can be called from the driver.
+template <typename ToType> class Caster {
+ static ToType f(bool a) { return cast<bool, ToType>(a); }
+ static ToType f(int8_t a) { return cast<int8_t, ToType>(a); }
+ static ToType f(uint8_t a) { return cast<uint8_t, ToType>(a); }
+ static ToType f(int16_t a) { return cast<int16_t, ToType>(a); }
+ static ToType f(uint16_t a) { return cast<uint16_t, ToType>(a); }
+ static ToType f(int32_t a) { return cast<int32_t, ToType>(a); }
+ static ToType f(uint32_t a) { return cast<uint32_t, ToType>(a); }
+ static ToType f(int64_t a) { return cast<int64_t, ToType>(a); }
+ static ToType f(uint64_t a) { return cast<uint64_t, ToType>(a); }
+ static ToType f(float a) { return cast<float, ToType>(a); }
+ static ToType f(double a) { return cast<double, ToType>(a); }
+};
+
+// Comment out the definition of Caster<bool> because clang compiles
+// casts to bool using icmp instead of the desired cast instruction.
+// The corrected definitions are in test_cast_to_u1.ll.
+
+// template class Caster<bool>;
+
+template class Caster<int8_t>;
+template class Caster<uint8_t>;
+template class Caster<int16_t>;
+template class Caster<uint16_t>;
+template class Caster<int32_t>;
+template class Caster<uint32_t>;
+template class Caster<int64_t>;
+template class Caster<uint64_t>;
+template class Caster<float>;
+template class Caster<double>;
+
+// This function definition forces castBits<A,B>() to be instantiated
+// in the resulting bitcode file for the 4 relevant <A,B>
+// combinations, so that they can be called from the driver.
+double makeBitCasters() {
+ double Result = 0;
+ Result += castBits<uint32_t, float>(0);
+ Result += castBits<uint64_t, double>(0);
+ Result += castBits<float, uint32_t>(0);
+ Result += castBits<double, uint64_t>(0);
+ return Result;
+}
diff --git a/crosstest/test_cast.h b/crosstest/test_cast.h
new file mode 100644
index 0000000..bf59cd9
--- /dev/null
+++ b/crosstest/test_cast.h
@@ -0,0 +1,2 @@
+template <typename FromType, typename ToType> ToType cast(FromType a);
+template <typename FromType, typename ToType> ToType castBits(FromType a);
diff --git a/crosstest/test_cast_main.cpp b/crosstest/test_cast_main.cpp
new file mode 100644
index 0000000..330f984
--- /dev/null
+++ b/crosstest/test_cast_main.cpp
@@ -0,0 +1,225 @@
+/* crosstest.py --test=test_cast.cpp --test=test_cast_to_u1.ll \
+ --driver=test_cast_main.cpp --prefix=Subzero_ --output=test_cast */
+
+#include <cstring>
+#include <iostream>
+#include <stdint.h>
+
+// Include test_cast.h twice - once normally, and once within the
+// Subzero_ namespace, corresponding to the llc and Subzero translated
+// object files, respectively.
+#include "test_cast.h"
+namespace Subzero_ {
+#include "test_cast.h"
+}
+
+#define XSTR(s) STR(s)
+#define STR(s) #s
+#define COMPARE(Func, FromCName, ToCName, Input) \
+ do { \
+ ToCName ResultSz, ResultLlc; \
+ ResultLlc = Func<FromCName, ToCName>(Input); \
+ ResultSz = Subzero_::Func<FromCName, ToCName>(Input); \
+ ++TotalTests; \
+ if (!memcmp(&ResultLlc, &ResultSz, sizeof(ToCName))) { \
+ ++Passes; \
+ } else { \
+ ++Failures; \
+ std::cout << std::fixed << XSTR(Func) \
+ << "<" XSTR(FromCName) ", " XSTR(ToCName) ">(" << Input \
+ << "): sz=" << ResultSz << " llc=" << ResultLlc << "\n"; \
+ } \
+ } while (0)
+
+template <typename FromType>
+void testValue(FromType Val, size_t &TotalTests, size_t &Passes,
+ size_t &Failures) {
+ COMPARE(cast, FromType, bool, Val);
+ COMPARE(cast, FromType, uint8_t, Val);
+ COMPARE(cast, FromType, int8_t, Val);
+ COMPARE(cast, FromType, uint16_t, Val);
+ COMPARE(cast, FromType, int16_t, Val);
+ COMPARE(cast, FromType, uint32_t, Val);
+ COMPARE(cast, FromType, int32_t, Val);
+ COMPARE(cast, FromType, uint64_t, Val);
+ COMPARE(cast, FromType, int64_t, Val);
+ COMPARE(cast, FromType, float, Val);
+ COMPARE(cast, FromType, double, Val);
+}
+
+int main(int argc, char **argv) {
+ size_t TotalTests = 0;
+ size_t Passes = 0;
+ size_t Failures = 0;
+
+ volatile bool ValsUi1[] = { false, true };
+ static const size_t NumValsUi1 = sizeof(ValsUi1) / sizeof(*ValsUi1);
+ volatile uint8_t ValsUi8[] = { 0, 1, 0x7e, 0x7f, 0x80, 0x81, 0xfe, 0xff };
+ static const size_t NumValsUi8 = sizeof(ValsUi8) / sizeof(*ValsUi8);
+
+ volatile int8_t ValsSi8[] = { 0, 1, 0x7e, 0x7f, 0x80, 0x81, 0xfe, 0xff };
+ static const size_t NumValsSi8 = sizeof(ValsSi8) / sizeof(*ValsSi8);
+
+ volatile uint16_t ValsUi16[] = { 0, 1, 0x7e, 0x7f, 0x80,
+ 0x81, 0xfe, 0xff, 0x7ffe, 0x7fff,
+ 0x8000, 0x8001, 0xfffe, 0xffff };
+ static const size_t NumValsUi16 = sizeof(ValsUi16) / sizeof(*ValsUi16);
+
+ volatile int16_t ValsSi16[] = { 0, 1, 0x7e, 0x7f, 0x80,
+ 0x81, 0xfe, 0xff, 0x7ffe, 0x7fff,
+ 0x8000, 0x8001, 0xfffe, 0xffff };
+ static const size_t NumValsSi16 = sizeof(ValsSi16) / sizeof(*ValsSi16);
+
+ volatile size_t ValsUi32[] = {
+ 0, 1, 0x7e, 0x7f, 0x80,
+ 0x81, 0xfe, 0xff, 0x7ffe, 0x7fff,
+ 0x8000, 0x8001, 0xfffe, 0xffff, 0x7ffffffe,
+ 0x7fffffff, 0x80000000, 0x80000001, 0xfffffffe, 0xffffffff
+ };
+ static const size_t NumValsUi32 = sizeof(ValsUi32) / sizeof(*ValsUi32);
+
+ volatile size_t ValsSi32[] = {
+ 0, 1, 0x7e, 0x7f, 0x80,
+ 0x81, 0xfe, 0xff, 0x7ffe, 0x7fff,
+ 0x8000, 0x8001, 0xfffe, 0xffff, 0x7ffffffe,
+ 0x7fffffff, 0x80000000, 0x80000001, 0xfffffffe, 0xffffffff
+ };
+ static const size_t NumValsSi32 = sizeof(ValsSi32) / sizeof(*ValsSi32);
+
+ volatile uint64_t ValsUi64[] = {
+ 0, 1, 0x7e,
+ 0x7f, 0x80, 0x81,
+ 0xfe, 0xff, 0x7ffe,
+ 0x7fff, 0x8000, 0x8001,
+ 0xfffe, 0xffff, 0x7ffffffe,
+ 0x7fffffff, 0x80000000, 0x80000001,
+ 0xfffffffe, 0xffffffff, 0x100000000ull,
+ 0x100000001ull, 0x7ffffffffffffffeull, 0x7fffffffffffffffull,
+ 0x8000000000000000ull, 0x8000000000000001ull, 0xfffffffffffffffeull,
+ 0xffffffffffffffffull
+ };
+ static const size_t NumValsUi64 = sizeof(ValsUi64) / sizeof(*ValsUi64);
+
+ volatile int64_t ValsSi64[] = {
+ 0, 1, 0x7e,
+ 0x7f, 0x80, 0x81,
+ 0xfe, 0xff, 0x7ffe,
+ 0x7fff, 0x8000, 0x8001,
+ 0xfffe, 0xffff, 0x7ffffffe,
+ 0x7fffffff, 0x80000000, 0x80000001,
+ 0xfffffffe, 0xffffffff, 0x100000000ll,
+ 0x100000001ll, 0x7ffffffffffffffell, 0x7fffffffffffffffll,
+ 0x8000000000000000ll, 0x8000000000000001ll, 0xfffffffffffffffell,
+ 0xffffffffffffffffll
+ };
+ static const size_t NumValsSi64 = sizeof(ValsSi64) / sizeof(*ValsSi64);
+
+ volatile float ValsF32[] = {
+ 0, 1, 0x7e,
+ 0x7f, 0x80, 0x81,
+ 0xfe, 0xff, 0x7ffe,
+ 0x7fff, 0x8000, 0x8001,
+ 0xfffe, 0xffff, 0x7ffffffe,
+ 0x7fffffff, 0x80000000, 0x80000001,
+ 0xfffffffe, 0xffffffff, 0x100000000ll,
+ 0x100000001ll, 0x7ffffffffffffffell, 0x7fffffffffffffffll,
+ 0x8000000000000000ll, 0x8000000000000001ll, 0xfffffffffffffffell,
+ 0xffffffffffffffffll
+ };
+ static const size_t NumValsF32 = sizeof(ValsF32) / sizeof(*ValsF32);
+
+ volatile double ValsF64[] = {
+ 0, 1, 0x7e,
+ 0x7f, 0x80, 0x81,
+ 0xfe, 0xff, 0x7ffe,
+ 0x7fff, 0x8000, 0x8001,
+ 0xfffe, 0xffff, 0x7ffffffe,
+ 0x7fffffff, 0x80000000, 0x80000001,
+ 0xfffffffe, 0xffffffff, 0x100000000ll,
+ 0x100000001ll, 0x7ffffffffffffffell, 0x7fffffffffffffffll,
+ 0x8000000000000000ll, 0x8000000000000001ll, 0xfffffffffffffffell,
+ 0xffffffffffffffffll
+ };
+ static const size_t NumValsF64 = sizeof(ValsF64) / sizeof(*ValsF64);
+
+ for (size_t i = 0; i < NumValsUi1; ++i) {
+ bool Val = ValsUi1[i];
+ testValue<bool>(Val, TotalTests, Passes, Failures);
+ }
+ for (size_t i = 0; i < NumValsUi8; ++i) {
+ uint8_t Val = ValsUi8[i];
+ testValue<uint8_t>(Val, TotalTests, Passes, Failures);
+ }
+ for (size_t i = 0; i < NumValsSi8; ++i) {
+ int8_t Val = ValsSi8[i];
+ testValue<int8_t>(Val, TotalTests, Passes, Failures);
+ }
+ for (size_t i = 0; i < NumValsUi16; ++i) {
+ uint16_t Val = ValsUi16[i];
+ testValue<uint16_t>(Val, TotalTests, Passes, Failures);
+ }
+ for (size_t i = 0; i < NumValsSi16; ++i) {
+ int16_t Val = ValsSi16[i];
+ testValue<int16_t>(Val, TotalTests, Passes, Failures);
+ }
+ for (size_t i = 0; i < NumValsUi32; ++i) {
+ uint32_t Val = ValsUi32[i];
+ testValue<uint32_t>(Val, TotalTests, Passes, Failures);
+ COMPARE(castBits, uint32_t, float, Val);
+ }
+ for (size_t i = 0; i < NumValsSi32; ++i) {
+ int32_t Val = ValsSi32[i];
+ testValue<int32_t>(Val, TotalTests, Passes, Failures);
+ }
+ for (size_t i = 0; i < NumValsUi64; ++i) {
+ uint64_t Val = ValsUi64[i];
+ testValue<uint64_t>(Val, TotalTests, Passes, Failures);
+ COMPARE(castBits, uint64_t, double, Val);
+ }
+ for (size_t i = 0; i < NumValsSi64; ++i) {
+ int64_t Val = ValsSi64[i];
+ testValue<int64_t>(Val, TotalTests, Passes, Failures);
+ }
+ for (size_t i = 0; i < NumValsF32; ++i) {
+ for (unsigned j = 0; j < 2; ++j) {
+ float Val = ValsF32[i];
+ if (j > 0)
+ Val = -Val;
+ testValue<float>(Val, TotalTests, Passes, Failures);
+ COMPARE(castBits, float, uint32_t, Val);
+ }
+ }
+ for (size_t i = 0; i < NumValsF64; ++i) {
+ for (unsigned j = 0; j < 2; ++j) {
+ double Val = ValsF64[i];
+ if (j > 0)
+ Val = -Val;
+ testValue<double>(Val, TotalTests, Passes, Failures);
+ COMPARE(castBits, double, uint64_t, Val);
+ }
+ }
+
+ std::cout << "TotalTests=" << TotalTests << " Passes=" << Passes
+ << " Failures=" << Failures << "\n";
+ return Failures;
+}
+
+////////////////////////////////////////////////////////////////
+
+// The following are helper definitions that should be part of the
+// Subzero runtime.
+
+extern "C" {
+uint32_t cvtdtoui32(double a) { return (uint32_t)a; }
+uint32_t cvtftoui32(float a) { return (uint32_t)a; }
+int64_t cvtdtosi64(double a) { return (int64_t)a; }
+int64_t cvtftosi64(float a) { return (int64_t)a; }
+uint64_t cvtdtoui64(double a) { return (uint64_t)a; }
+uint64_t cvtftoui64(float a) { return (uint64_t)a; }
+float cvtui64tof(uint64_t a) { return (float)a; }
+double cvtui64tod(uint64_t a) { return (double)a; }
+float cvtsi64tof(int64_t a) { return (float)a; }
+float cvtui32tof(uint32_t a) { return (float)a; }
+double cvtui32tod(uint32_t a) { return (double)a; }
+double cvtsi64tod(int64_t a) { return (double)a; }
+}
diff --git a/crosstest/test_cast_to_u1.ll b/crosstest/test_cast_to_u1.ll
new file mode 100644
index 0000000..f8a9ec6
--- /dev/null
+++ b/crosstest/test_cast_to_u1.ll
@@ -0,0 +1,92 @@
+target triple = "i686-pc-linux-gnu"
+
+define i32 @_Z4castIxbET0_T_(i64 %a) {
+entry:
+; %tobool = icmp ne i64 %a, 0
+ %tobool = trunc i64 %a to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
+
+define i32 @_Z4castIybET0_T_(i64 %a) {
+entry:
+; %tobool = icmp ne i64 %a, 0
+ %tobool = trunc i64 %a to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
+
+define i32 @_Z4castIibET0_T_(i32 %a) {
+entry:
+; %tobool = icmp ne i32 %a, 0
+ %tobool = trunc i32 %a to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
+
+define i32 @_Z4castIjbET0_T_(i32 %a) {
+entry:
+; %tobool = icmp ne i32 %a, 0
+ %tobool = trunc i32 %a to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
+
+define i32 @_Z4castIsbET0_T_(i32 %a) {
+entry:
+ %a.arg_trunc = trunc i32 %a to i16
+; %tobool = icmp ne i16 %a.arg_trunc, 0
+ %tobool = trunc i16 %a.arg_trunc to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
+
+define i32 @_Z4castItbET0_T_(i32 %a) {
+entry:
+ %a.arg_trunc = trunc i32 %a to i16
+; %tobool = icmp ne i16 %a.arg_trunc, 0
+ %tobool = trunc i16 %a.arg_trunc to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
+
+define i32 @_Z4castIabET0_T_(i32 %a) {
+entry:
+ %a.arg_trunc = trunc i32 %a to i8
+; %tobool = icmp ne i8 %a.arg_trunc, 0
+ %tobool = trunc i8 %a.arg_trunc to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
+
+define i32 @_Z4castIhbET0_T_(i32 %a) {
+entry:
+ %a.arg_trunc = trunc i32 %a to i8
+; %tobool = icmp ne i8 %a.arg_trunc, 0
+ %tobool = trunc i8 %a.arg_trunc to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
+
+define i32 @_Z4castIbbET0_T_(i32 %a) {
+entry:
+ %a.arg_trunc = trunc i32 %a to i1
+ %a.arg_trunc.ret_ext = zext i1 %a.arg_trunc to i32
+ ret i32 %a.arg_trunc.ret_ext
+}
+
+define i32 @_Z4castIdbET0_T_(double %a) {
+entry:
+; %tobool = fcmp une double %a, 0.000000e+00
+ %tobool = fptoui double %a to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
+
+define i32 @_Z4castIfbET0_T_(float %a) {
+entry:
+; %tobool = fcmp une float %a, 0.000000e+00
+ %tobool = fptoui float %a to i1
+ %tobool.ret_ext = zext i1 %tobool to i32
+ ret i32 %tobool.ret_ext
+}
diff --git a/crosstest/test_fcmp.def b/crosstest/test_fcmp.def
new file mode 100644
index 0000000..9f498b4
--- /dev/null
+++ b/crosstest/test_fcmp.def
@@ -0,0 +1,27 @@
+#ifndef TEST_FCMP_DEF
+#define TEST_FCMP_DEF
+
+#define XSTR(s) STR(s)
+#define STR(s) #s
+
+#define FCMP_TABLE \
+ /* cmp */ \
+ X(False) \
+ X(Oeq) \
+ X(Ogt) \
+ X(Oge) \
+ X(Olt) \
+ X(Ole) \
+ X(One) \
+ X(Ord) \
+ X(Ueq) \
+ X(Ugt) \
+ X(Uge) \
+ X(Ult) \
+ X(Ule) \
+ X(Une) \
+ X(Uno) \
+ X(True) \
+//#define X(cmp)
+
+#endif // TEST_FCMP_DEF
diff --git a/crosstest/test_fcmp.pnacl.ll b/crosstest/test_fcmp.pnacl.ll
new file mode 100644
index 0000000..7c4d42e
--- /dev/null
+++ b/crosstest/test_fcmp.pnacl.ll
@@ -0,0 +1,324 @@
+target triple = "i686-pc-linux-gnu"
+
+; This file is extracted from fp.pnacl.ll in the lit tests, with
+; the "internal" attribute removed from the functions.
+
+define i32 @fcmpFalseFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp false float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpFalseFloat:
+; CHECK: mov {{.*}}, 0
+
+define i32 @fcmpFalseDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp false double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpFalseDouble:
+; CHECK: mov {{.*}}, 0
+
+define i32 @fcmpOeqFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp oeq float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOeqFloat:
+; CHECK: ucomiss
+; CHECK: jne .
+; CHECK: jp .
+
+define i32 @fcmpOeqDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp oeq double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOeqDouble:
+; CHECK: ucomisd
+; CHECK: jne .
+; CHECK: jp .
+
+define i32 @fcmpOgtFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp ogt float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOgtFloat:
+; CHECK: ucomiss
+; CHECK: ja .
+
+define i32 @fcmpOgtDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp ogt double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOgtDouble:
+; CHECK: ucomisd
+; CHECK: ja .
+
+define i32 @fcmpOgeFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp oge float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOgeFloat:
+; CHECK: ucomiss
+; CHECK: jae .
+
+define i32 @fcmpOgeDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp oge double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOgeDouble:
+; CHECK: ucomisd
+; CHECK: jae .
+
+define i32 @fcmpOltFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp olt float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOltFloat:
+; CHECK: ucomiss
+; CHECK: ja .
+
+define i32 @fcmpOltDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp olt double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOltDouble:
+; CHECK: ucomisd
+; CHECK: ja .
+
+define i32 @fcmpOleFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp ole float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOleFloat:
+; CHECK: ucomiss
+; CHECK: jae .
+
+define i32 @fcmpOleDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp ole double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOleDouble:
+; CHECK: ucomisd
+; CHECK: jae .
+
+define i32 @fcmpOneFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp one float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOneFloat:
+; CHECK: ucomiss
+; CHECK: jne .
+
+define i32 @fcmpOneDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp one double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOneDouble:
+; CHECK: ucomisd
+; CHECK: jne .
+
+define i32 @fcmpOrdFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp ord float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOrdFloat:
+; CHECK: ucomiss
+; CHECK: jnp .
+
+define i32 @fcmpOrdDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp ord double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpOrdDouble:
+; CHECK: ucomisd
+; CHECK: jnp .
+
+define i32 @fcmpUeqFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp ueq float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUeqFloat:
+; CHECK: ucomiss
+; CHECK: je .
+
+define i32 @fcmpUeqDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp ueq double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUeqDouble:
+; CHECK: ucomisd
+; CHECK: je .
+
+define i32 @fcmpUgtFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp ugt float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUgtFloat:
+; CHECK: ucomiss
+; CHECK: jb .
+
+define i32 @fcmpUgtDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp ugt double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUgtDouble:
+; CHECK: ucomisd
+; CHECK: jb .
+
+define i32 @fcmpUgeFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp uge float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUgeFloat:
+; CHECK: ucomiss
+; CHECK: jbe .
+
+define i32 @fcmpUgeDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp uge double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUgeDouble:
+; CHECK: ucomisd
+; CHECK: jbe .
+
+define i32 @fcmpUltFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp ult float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUltFloat:
+; CHECK: ucomiss
+; CHECK: jb .
+
+define i32 @fcmpUltDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp ult double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUltDouble:
+; CHECK: ucomisd
+; CHECK: jb .
+
+define i32 @fcmpUleFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp ule float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUleFloat:
+; CHECK: ucomiss
+; CHECK: jbe .
+
+define i32 @fcmpUleDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp ule double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUleDouble:
+; CHECK: ucomisd
+; CHECK: jbe .
+
+define i32 @fcmpUneFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp une float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUneFloat:
+; CHECK: ucomiss
+; CHECK: je .
+; CHECK: jnp .
+
+define i32 @fcmpUneDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp une double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUneDouble:
+; CHECK: ucomisd
+; CHECK: je .
+; CHECK: jnp .
+
+define i32 @fcmpUnoFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp uno float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUnoFloat:
+; CHECK: ucomiss
+; CHECK: jp .
+
+define i32 @fcmpUnoDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp uno double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpUnoDouble:
+; CHECK: ucomisd
+; CHECK: jp .
+
+define i32 @fcmpTrueFloat(float %a, float %b) {
+entry:
+ %cmp = fcmp true float %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpTrueFloat:
+; CHECK: mov {{.*}}, 1
+
+define i32 @fcmpTrueDouble(double %a, double %b) {
+entry:
+ %cmp = fcmp true double %a, %b
+ %cmp.ret_ext = zext i1 %cmp to i32
+ ret i32 %cmp.ret_ext
+}
+; CHECK: fcmpTrueDouble:
+; CHECK: mov {{.*}}, 1
diff --git a/crosstest/test_fcmp_main.cpp b/crosstest/test_fcmp_main.cpp
new file mode 100644
index 0000000..8677c48
--- /dev/null
+++ b/crosstest/test_fcmp_main.cpp
@@ -0,0 +1,98 @@
+/* crosstest.py --test=test_fcmp.pnacl.ll --driver=test_fcmp_main.cpp \
+ --prefix=Subzero_ --output=test_fcmp */
+
+#include <cassert>
+#include <cfloat>
+#include <cmath>
+#include <iostream>
+
+#include "test_fcmp.def"
+
+#define X(cmp) \
+ extern "C" bool fcmp##cmp##Float(float a, float b); \
+ extern "C" bool fcmp##cmp##Double(double a, double b); \
+ extern "C" bool Subzero_fcmp##cmp##Float(float a, float b); \
+ extern "C" bool Subzero_fcmp##cmp##Double(double a, double b);
+FCMP_TABLE;
+#undef X
+
+int main(int argc, char **argv) {
+ static const double NegInf = -1.0 / 0.0;
+ static const double Zero = 0.0;
+ static const double Ten = 10.0;
+ static const double PosInf = 1.0 / 0.0;
+ static const double Nan = 0.0 / 0.0;
+ assert(std::fpclassify(NegInf) == FP_INFINITE);
+ assert(std::fpclassify(PosInf) == FP_INFINITE);
+ assert(std::fpclassify(Nan) == FP_NAN);
+ assert(NegInf < Zero);
+ assert(NegInf < PosInf);
+ assert(Zero < PosInf);
+
+ volatile double Values[] = { NegInf, Zero, DBL_MIN, FLT_MIN, Ten,
+ FLT_MAX, DBL_MAX, PosInf, Nan, };
+ const static size_t NumValues = sizeof(Values) / sizeof(*Values);
+
+ typedef bool (*FuncTypeFloat)(float, float);
+ typedef bool (*FuncTypeDouble)(double, double);
+ static struct {
+ const char *Name;
+ FuncTypeFloat FuncFloatSz;
+ FuncTypeFloat FuncFloatLlc;
+ FuncTypeDouble FuncDoubleSz;
+ FuncTypeDouble FuncDoubleLlc;
+ } Funcs[] = {
+#define X(cmp) \
+ { \
+ "fcmp" STR(cmp), Subzero_fcmp##cmp##Float, fcmp##cmp##Float, \
+ Subzero_fcmp##cmp##Double, fcmp##cmp##Double \
+ } \
+ ,
+ FCMP_TABLE
+#undef X
+ };
+ const static size_t NumFuncs = sizeof(Funcs) / sizeof(*Funcs);
+
+ bool ResultSz, ResultLlc;
+
+ size_t TotalTests = 0;
+ size_t Passes = 0;
+ size_t Failures = 0;
+
+ for (size_t f = 0; f < NumFuncs; ++f) {
+ for (size_t i = 0; i < NumValues; ++i) {
+ for (size_t j = 0; j < NumValues; ++j) {
+ ++TotalTests;
+ float Value1Float = Values[i];
+ float Value2Float = Values[j];
+ ResultSz = Funcs[f].FuncFloatSz(Value1Float, Value2Float);
+ ResultLlc = Funcs[f].FuncFloatLlc(Value1Float, Value2Float);
+ if (ResultSz == ResultLlc) {
+ ++Passes;
+ } else {
+ ++Failures;
+ std::cout << Funcs[f].Name << "Float(" << Value1Float << ", "
+ << Value2Float << "): sz=" << ResultSz
+ << " llc=" << ResultLlc << std::endl;
+ }
+ ++TotalTests;
+ double Value1Double = Values[i];
+ double Value2Double = Values[j];
+ ResultSz = Funcs[f].FuncDoubleSz(Value1Double, Value2Double);
+ ResultLlc = Funcs[f].FuncDoubleLlc(Value1Double, Value2Double);
+ if (ResultSz == ResultLlc) {
+ ++Passes;
+ } else {
+ ++Failures;
+ std::cout << Funcs[f].Name << "Double(" << Value1Double << ", "
+ << Value2Double << "): sz=" << ResultSz
+ << " llc=" << ResultLlc << std::endl;
+ }
+ }
+ }
+ }
+
+ std::cout << "TotalTests=" << TotalTests << " Passes=" << Passes
+ << " Failures=" << Failures << "\n";
+ return Failures;
+}
diff --git a/crosstest/test_icmp.cpp b/crosstest/test_icmp.cpp
new file mode 100644
index 0000000..f1b144d
--- /dev/null
+++ b/crosstest/test_icmp.cpp
@@ -0,0 +1,21 @@
+// This aims to test the icmp bitcode instruction across all PNaCl
+// primitive integer types.
+
+#include <stdint.h>
+
+#include "test_icmp.h"
+
+#define X(cmp, op) \
+ bool icmp##cmp(uint8_t a, uint8_t b) { return a op b; } \
+ bool icmp##cmp(uint16_t a, uint16_t b) { return a op b; } \
+ bool icmp##cmp(uint32_t a, uint32_t b) { return a op b; } \
+ bool icmp##cmp(uint64_t a, uint64_t b) { return a op b; }
+ICMP_U_TABLE
+#undef X
+#define X(cmp, op) \
+ bool icmp##cmp(int8_t a, int8_t b) { return a op b; } \
+ bool icmp##cmp(int16_t a, int16_t b) { return a op b; } \
+ bool icmp##cmp(int32_t a, int32_t b) { return a op b; } \
+ bool icmp##cmp(int64_t a, int64_t b) { return a op b; }
+ICMP_S_TABLE
+#undef X
diff --git a/crosstest/test_icmp.def b/crosstest/test_icmp.def
new file mode 100644
index 0000000..c7cfc96
--- /dev/null
+++ b/crosstest/test_icmp.def
@@ -0,0 +1,25 @@
+#ifndef TEST_ICMP_DEF
+#define TEST_ICMP_DEF
+
+#define XSTR(s) STR(s)
+#define STR(s) #s
+
+#define ICMP_U_TABLE \
+ /* cmp, operator */ \
+ X(Eq, ==) \
+ X(Ne, !=) \
+ X(Ugt, >) \
+ X(Uge, >=) \
+ X(Ult, <) \
+ X(Ule, <=) \
+//#define X(cmp, op)
+
+#define ICMP_S_TABLE \
+ /* cmp, operator */ \
+ X(Sgt, >) \
+ X(Sge, >=) \
+ X(Slt, <) \
+ X(Sle, <=) \
+//#define X(cmp, op)
+
+#endif // TEST_ICMP_DEF
diff --git a/crosstest/test_icmp.h b/crosstest/test_icmp.h
new file mode 100644
index 0000000..d4ce9f1
--- /dev/null
+++ b/crosstest/test_icmp.h
@@ -0,0 +1,17 @@
+#include "test_icmp.def"
+
+#define X(cmp, op) \
+ bool icmp##cmp(uint8_t a, uint8_t b); \
+ bool icmp##cmp(uint16_t a, uint16_t b); \
+ bool icmp##cmp(uint32_t a, uint32_t b); \
+ bool icmp##cmp(uint64_t a, uint64_t b);
+ICMP_U_TABLE
+#undef X
+
+#define X(cmp, op) \
+ bool icmp##cmp(int8_t a, int8_t b); \
+ bool icmp##cmp(int16_t a, int16_t b); \
+ bool icmp##cmp(int32_t a, int32_t b); \
+ bool icmp##cmp(int64_t a, int64_t b);
+ICMP_S_TABLE
+#undef X
diff --git a/crosstest/test_icmp_main.cpp b/crosstest/test_icmp_main.cpp
new file mode 100644
index 0000000..3981fcf
--- /dev/null
+++ b/crosstest/test_icmp_main.cpp
@@ -0,0 +1,118 @@
+/* crosstest.py --test=test_icmp.cpp --driver=test_icmp_main.cpp \
+ --prefix=Subzero_ --output=test_icmp */
+
+#include <stdint.h>
+#include <iostream>
+
+// Include test_icmp.h twice - once normally, and once within the
+// Subzero_ namespace, corresponding to the llc and Subzero translated
+// object files, respectively.
+#include "test_icmp.h"
+namespace Subzero_ {
+#include "test_icmp.h"
+}
+
+volatile unsigned Values[] = { 0x0, 0x1, 0x7ffffffe, 0x7fffffff,
+ 0x80000000, 0x80000001, 0xfffffffe, 0xffffffff,
+ 0x7e, 0x7f, 0x80, 0x81,
+ 0xfe, 0xff, 0x100, 0x101,
+ 0x7ffe, 0x7fff, 0x8000, 0x8001,
+ 0xfffe, 0xffff, 0x10000, 0x10001, };
+const static size_t NumValues = sizeof(Values) / sizeof(*Values);
+
+template <typename TypeUnsigned, typename TypeSigned>
+void testsInt(size_t &TotalTests, size_t &Passes, size_t &Failures) {
+ typedef bool (*FuncTypeUnsigned)(TypeUnsigned, TypeUnsigned);
+ typedef bool (*FuncTypeSigned)(TypeSigned, TypeSigned);
+ static struct {
+ const char *Name;
+ FuncTypeUnsigned FuncLlc;
+ FuncTypeUnsigned FuncSz;
+ } Funcs[] = {
+#define X(cmp, op) \
+ { \
+ STR(inst), (FuncTypeUnsigned)icmp##cmp, \
+ (FuncTypeUnsigned)Subzero_::icmp##cmp \
+ } \
+ ,
+ ICMP_U_TABLE
+#undef X
+#define X(cmp, op) \
+ { \
+ STR(inst), (FuncTypeUnsigned)(FuncTypeSigned)icmp##cmp, \
+ (FuncTypeUnsigned)(FuncTypeSigned)Subzero_::icmp##cmp \
+ } \
+ ,
+ ICMP_S_TABLE
+#undef X
+ };
+ const static size_t NumFuncs = sizeof(Funcs) / sizeof(*Funcs);
+
+ if (sizeof(TypeUnsigned) <= sizeof(uint32_t)) {
+ // This is the "normal" version of the loop nest, for 32-bit or
+ // narrower types.
+ for (size_t f = 0; f < NumFuncs; ++f) {
+ for (size_t i = 0; i < NumValues; ++i) {
+ for (size_t j = 0; j < NumValues; ++j) {
+ TypeUnsigned Value1 = Values[i];
+ TypeUnsigned Value2 = Values[j];
+ ++TotalTests;
+ bool ResultSz = Funcs[f].FuncSz(Value1, Value2);
+ bool ResultLlc = Funcs[f].FuncLlc(Value1, Value2);
+ if (ResultSz == ResultLlc) {
+ ++Passes;
+ } else {
+ ++Failures;
+ std::cout << "icmp" << Funcs[f].Name << (8 * sizeof(TypeUnsigned))
+ << "(" << Value1 << ", " << Value2 << "): sz=" << ResultSz
+ << " llc=" << ResultLlc << std::endl;
+ }
+ }
+ }
+ }
+ } else {
+ // This is the 64-bit version. Test values are synthesized from
+ // the 32-bit values in Values[].
+ for (size_t f = 0; f < NumFuncs; ++f) {
+ for (size_t iLo = 0; iLo < NumValues; ++iLo) {
+ for (size_t iHi = 0; iHi < NumValues; ++iHi) {
+ for (size_t jLo = 0; jLo < NumValues; ++jLo) {
+ for (size_t jHi = 0; jHi < NumValues; ++jHi) {
+ TypeUnsigned Value1 =
+ (((TypeUnsigned)Values[iHi]) << 32) + Values[iLo];
+ TypeUnsigned Value2 =
+ (((TypeUnsigned)Values[jHi]) << 32) + Values[jLo];
+ ++TotalTests;
+ bool ResultSz = Funcs[f].FuncSz(Value1, Value2);
+ bool ResultLlc = Funcs[f].FuncLlc(Value1, Value2);
+ if (ResultSz == ResultLlc) {
+ ++Passes;
+ } else {
+ ++Failures;
+ std::cout << "icmp" << Funcs[f].Name
+ << (8 * sizeof(TypeUnsigned)) << "(" << Value1 << ", "
+ << Value2 << "): sz=" << ResultSz
+ << " llc=" << ResultLlc << std::endl;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+int main(int argc, char **argv) {
+ size_t TotalTests = 0;
+ size_t Passes = 0;
+ size_t Failures = 0;
+
+ testsInt<uint8_t, int8_t>(TotalTests, Passes, Failures);
+ testsInt<uint16_t, int16_t>(TotalTests, Passes, Failures);
+ testsInt<uint32_t, int32_t>(TotalTests, Passes, Failures);
+ testsInt<uint64_t, int64_t>(TotalTests, Passes, Failures);
+
+ std::cout << "TotalTests=" << TotalTests << " Passes=" << Passes
+ << " Failures=" << Failures << "\n";
+ return Failures;
+}