Commit 5bc2b1d1 by Jim Stichnoth

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
parent a667fb85
......@@ -10,3 +10,4 @@
# Ignore specific patterns at the top-level directory
/llvm2ice
/build/
/crosstest/Output/
This diff is collapsed. Click to expand it.
......@@ -29,7 +29,7 @@ LLVM_LDFLAGS := `$(LLVM_BIN_PATH)/llvm-config --ldflags --libs`
# It's recommended that CXX matches the compiler you used to build LLVM itself.
OPTLEVEL := -O0
CXX := g++
CXXFLAGS := -Wall -Werror -fno-rtti -fno-exceptions \
CXXFLAGS := -Wall -Wextra -Werror -fno-rtti -fno-exceptions \
$(OPTLEVEL) -g $(LLVM_CXXFLAGS) -m32
LDFLAGS := -m32
......@@ -38,7 +38,10 @@ SRCS= \
IceCfgNode.cpp \
IceGlobalContext.cpp \
IceInst.cpp \
IceInstX8632.cpp \
IceOperand.cpp \
IceTargetLowering.cpp \
IceTargetLoweringX8632.cpp \
IceTypes.cpp \
llvm2ice.cpp
......@@ -64,6 +67,7 @@ build:
check: llvm2ice
LLVM_BIN_PATH=$(LLVM_BIN_PATH) \
$(LLVM_SRC_PATH)/utils/lit/lit.py -sv tests_lit
(cd crosstest; LLVM_BIN_PATH=$(LLVM_BIN_PATH) ./runtests.sh)
# TODO: Fix the use of wildcards.
format:
......
......@@ -34,15 +34,24 @@ At this time, ``llvm2ice`` accepts a few arguments:
``-notranslate`` -- Suppress the ICE translation phase, which is useful if
ICE is missing some support.
``-target=<TARGET>`` -- Set the target architecture. The default is x8632,
and x8632fast (generate x8632 code as fast as possible at the cost of code
quality) is also available. Future targets include x8664, arm32, and arm64.
``-target=<TARGET>`` -- Set the target architecture. The default is x8632.
Future targets include x8664, arm32, and arm64.
``-O<LEVEL>`` -- Set the optimization level. Valid levels are ``2``, ``1``,
``0``, ``-1``, and ``m1``. Levels ``-1`` and ``m1`` are synonyms, and
represent the minimum optimization and worst code quality, but fastest code
generation.
``-verbose=<list>`` -- Set verbosity flags. This argument allows a
comma-separated list of values. The default is ``none``, and the value
``inst,pred`` will roughly match the .ll bitcode file. Of particular use
are ``all`` and ``none``.
``-o <FILE>`` -- Set the assembly output file name. Default is stdout.
``-log <FILE>`` -- Set the file name for diagnostic output (whose level is
controlled by ``-verbose``). Default is stdout.
See ir_samples/README.rst for more details.
Running the test suite
......
#!/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)])
#!/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
// 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;
}
/* 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;
}
// 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
#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
#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
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
}
/* 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;
}
// 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;
}
template <typename FromType, typename ToType> ToType cast(FromType a);
template <typename FromType, typename ToType> ToType castBits(FromType a);
/* 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; }
}
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
}
#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
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
/* 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;
}
// 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
#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
#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
/* 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;
}
#!/usr/bin/env python2
import argparse
import os
import sys
import tempfile
from utils import shellcmd
if __name__ == '__main__':
argparser = argparse.ArgumentParser()
argparser.add_argument('cfile', nargs='+', type=str,
help='C file(s) to convert')
argparser.add_argument('--nacl_sdk_root', nargs='?', type=str,
help='Path to NACL_SDK_ROOT')
argparser.add_argument('--dir', nargs='?', type=str, default='.',
help='Output directory')
argparser.add_argument('--disable-verify', action='store_true')
args = argparser.parse_args()
nacl_sdk_root = os.environ.get('NACL_SDK_ROOT', None)
if args.nacl_sdk_root:
nacl_sdk_root = os.path.expanduser(args.nacl_sdk_root)
if not nacl_sdk_root or not os.path.exists(nacl_sdk_root):
print '''\
Please set the NACL_SDK_ROOT environment variable or pass the path through
--nacl_sdk_root to point to a valid Native Client SDK installation.'''
sys.exit(1)
includes_path = os.path.join(nacl_sdk_root, 'include')
toolchain_path = os.path.join(nacl_sdk_root, 'toolchain', 'linux_pnacl')
clang_path = os.path.join(toolchain_path, 'bin64', 'pnacl-clang')
opt_path = os.path.join(toolchain_path, 'host_x86_64', 'bin', 'opt')
tempdir = tempfile.mkdtemp()
for cname in args.cfile:
basename = os.path.splitext(cname)[0]
llname = os.path.join(tempdir, basename + '.ll')
pnaclname = basename + '.pnacl.ll'
pnaclname = os.path.join(args.dir, pnaclname)
shellcmd(clang_path + ' -I{0} -c {1} -o {2}'.format(
includes_path, cname, llname))
shellcmd(opt_path +
' -O2 -pnacl-abi-simplify-preopt -pnacl-abi-simplify-postopt' +
('' if args.disable_verify else
' -verify-pnaclabi-module -verify-pnaclabi-functions') +
' -pnaclabi-allow-debug-metadata -disable-simplify-libcalls'
' {0} -S -o {1}'.format(llname, pnaclname))
......@@ -17,13 +17,16 @@
#include "IceDefs.h"
#include "IceInst.h"
#include "IceOperand.h"
#include "IceTargetLowering.h"
namespace Ice {
Cfg::Cfg(GlobalContext *Ctx)
: Ctx(Ctx), FunctionName(""), ReturnType(IceType_void),
IsInternalLinkage(false), HasError(false), ErrorMessage(""), Entry(NULL),
NextInstNumber(1), CurrentNode(NULL) {}
NextInstNumber(1),
Target(TargetLowering::createLowering(Ctx->getTargetArch(), this)),
CurrentNode(NULL) {}
Cfg::~Cfg() {}
......@@ -54,14 +57,107 @@ void Cfg::addArg(Variable *Arg) {
Args.push_back(Arg);
}
// Returns whether the stack frame layout has been computed yet. This
// is used for dumping the stack frame location of Variables.
bool Cfg::hasComputedFrame() const { return getTarget()->hasComputedFrame(); }
void Cfg::translate() {
Ostream &Str = Ctx->getStrDump();
if (hasError())
return;
if (Ctx->isVerbose()) {
Str << "================ Initial CFG ================\n";
dump();
}
Timer T_translate;
// The set of translation passes and their order are determined by
// the target.
getTarget()->translate();
T_translate.printElapsedUs(getContext(), "translate()");
if (Ctx->isVerbose()) {
Str << "================ Final output ================\n";
dump();
}
}
void Cfg::computePredecessors() {
for (NodeList::iterator I = Nodes.begin(), E = Nodes.end(); I != E; ++I) {
(*I)->computePredecessors();
}
}
// placePhiLoads() must be called before placePhiStores().
void Cfg::placePhiLoads() {
for (NodeList::iterator I = Nodes.begin(), E = Nodes.end(); I != E; ++I) {
(*I)->placePhiLoads();
}
}
// placePhiStores() must be called after placePhiLoads().
void Cfg::placePhiStores() {
for (NodeList::iterator I = Nodes.begin(), E = Nodes.end(); I != E; ++I) {
(*I)->placePhiStores();
}
}
void Cfg::deletePhis() {
for (NodeList::iterator I = Nodes.begin(), E = Nodes.end(); I != E; ++I) {
(*I)->deletePhis();
}
}
void Cfg::genCode() {
for (NodeList::iterator I = Nodes.begin(), E = Nodes.end(); I != E; ++I) {
(*I)->genCode();
}
}
// Compute the stack frame layout.
void Cfg::genFrame() {
getTarget()->addProlog(Entry);
// TODO: Consider folding epilog generation into the final
// emission/assembly pass to avoid an extra iteration over the node
// list. Or keep a separate list of exit nodes.
for (NodeList::iterator I = Nodes.begin(), E = Nodes.end(); I != E; ++I) {
CfgNode *Node = *I;
if (Node->getHasReturn())
getTarget()->addEpilog(Node);
}
}
// ======================== Dump routines ======================== //
void Cfg::emit() {
Ostream &Str = Ctx->getStrEmit();
Timer T_emit;
if (!Ctx->testAndSetHasEmittedFirstMethod()) {
// Print a helpful command for assembling the output.
// TODO: have the Target emit the header
// TODO: need a per-file emit in addition to per-CFG
Str << "# $LLVM_BIN_PATH/llvm-mc"
<< " -arch=x86"
<< " -x86-asm-syntax=intel"
<< " -filetype=obj"
<< " -o=MyObj.o"
<< "\n\n";
}
Str << "\t.text\n";
if (!getInternal()) {
IceString MangledName = getContext()->mangleName(getFunctionName());
Str << "\t.globl\t" << MangledName << "\n";
Str << "\t.type\t" << MangledName << ",@function\n";
}
for (NodeList::const_iterator I = Nodes.begin(), E = Nodes.end(); I != E;
++I) {
(*I)->emit(this);
}
Str << "\n";
T_emit.printElapsedUs(Ctx, "emit()");
}
void Cfg::dump() {
Ostream &Str = Ctx->getStrDump();
setCurrentNode(getEntryNode());
......
......@@ -58,7 +58,7 @@ public:
const NodeList &getNodes() const { return Nodes; }
// Manage instruction numbering.
int newInstNumber() { return NextInstNumber++; }
int32_t newInstNumber() { return NextInstNumber++; }
// Manage Variables.
Variable *makeVariable(Type Ty, const CfgNode *Node,
......@@ -70,16 +70,28 @@ public:
void addArg(Variable *Arg);
const VarList &getArgs() const { return Args; }
// Miscellaneous accessors.
TargetLowering *getTarget() const { return Target.get(); }
bool hasComputedFrame() const;
// Passes over the CFG.
void translate();
// After the CFG is fully constructed, iterate over the nodes and
// compute the predecessor edges, in the form of
// CfgNode::InEdges[].
void computePredecessors();
void placePhiLoads();
void placePhiStores();
void deletePhis();
void genCode();
void genFrame();
// Manage the CurrentNode field, which is used for validating the
// Variable::DefNode field during dumping/emitting.
void setCurrentNode(const CfgNode *Node) { CurrentNode = Node; }
const CfgNode *getCurrentNode() const { return CurrentNode; }
void emit();
void dump();
// Allocate data of type T using the per-Cfg allocator.
......@@ -124,9 +136,10 @@ private:
IceString ErrorMessage;
CfgNode *Entry; // entry basic block
NodeList Nodes; // linearized node list; Entry should be first
int NextInstNumber;
int32_t NextInstNumber;
VarList Variables;
VarList Args; // subset of Variables, in argument order
llvm::OwningPtr<TargetLowering> Target;
// CurrentNode is maintained during dumping/emitting just for
// validating Variable::DefNode. Normally, a traversal over
......
......@@ -7,8 +7,8 @@
//
//===----------------------------------------------------------------------===//
//
// This file implements the CfgNode class, including the
// complexities of instruction insertion and in-edge calculation.
// This file implements the CfgNode class, including the complexities
// of instruction insertion and in-edge calculation.
//
//===----------------------------------------------------------------------===//
......@@ -16,11 +16,12 @@
#include "IceCfgNode.h"
#include "IceInst.h"
#include "IceOperand.h"
#include "IceTargetLowering.h"
namespace Ice {
CfgNode::CfgNode(Cfg *Func, SizeT LabelNumber, IceString Name)
: Func(Func), Number(LabelNumber), Name(Name) {}
: Func(Func), Number(LabelNumber), Name(Name), HasReturn(false) {}
// Returns the name the node was created with. If no name was given,
// it synthesizes a (hopefully) unique name.
......@@ -61,8 +62,135 @@ void CfgNode::computePredecessors() {
}
}
// This does part 1 of Phi lowering, by creating a new dest variable
// for each Phi instruction, replacing the Phi instruction's dest with
// that variable, and adding an explicit assignment of the old dest to
// the new dest. For example,
// a=phi(...)
// changes to
// "a_phi=phi(...); a=a_phi".
//
// This is in preparation for part 2 which deletes the Phi
// instructions and appends assignment instructions to predecessor
// blocks. Note that this transformation preserves SSA form.
void CfgNode::placePhiLoads() {
for (PhiList::iterator I = Phis.begin(), E = Phis.end(); I != E; ++I) {
Inst *Inst = (*I)->lower(Func, this);
Insts.insert(Insts.begin(), Inst);
Inst->updateVars(this);
}
}
// This does part 2 of Phi lowering. For each Phi instruction at each
// out-edge, create a corresponding assignment instruction, and add
// all the assignments near the end of this block. They need to be
// added before any branch instruction, and also if the block ends
// with a compare instruction followed by a branch instruction that we
// may want to fuse, it's better to insert the new assignments before
// the compare instruction.
//
// Note that this transformation takes the Phi dest variables out of
// SSA form, as there may be assignments to the dest variable in
// multiple blocks.
//
// TODO: Defer this pass until after register allocation, then split
// critical edges, add the assignments, and lower them. This should
// reduce the amount of shuffling at the end of each block.
void CfgNode::placePhiStores() {
// Find the insertion point. TODO: After branch/compare fusing is
// implemented, try not to insert Phi stores between the compare and
// conditional branch instructions, otherwise the branch/compare
// pattern matching may fail. However, the branch/compare sequence
// will have to be broken if the compare result is read (by the
// assignment) before it is written (by the compare).
InstList::iterator InsertionPoint = Insts.end();
// Every block must end in a terminator instruction.
assert(InsertionPoint != Insts.begin());
--InsertionPoint;
// Confirm via assert() that InsertionPoint is a terminator
// instruction. Calling getTerminatorEdges() on a non-terminator
// instruction will cause an llvm_unreachable().
assert(((*InsertionPoint)->getTerminatorEdges(), true));
// Consider every out-edge.
for (NodeList::const_iterator I1 = OutEdges.begin(), E1 = OutEdges.end();
I1 != E1; ++I1) {
CfgNode *Target = *I1;
// Consider every Phi instruction at the out-edge.
for (PhiList::const_iterator I2 = Target->Phis.begin(),
E2 = Target->Phis.end();
I2 != E2; ++I2) {
Operand *Operand = (*I2)->getOperandForTarget(this);
assert(Operand);
Variable *Dest = (*I2)->getDest();
assert(Dest);
InstAssign *NewInst = InstAssign::create(Func, Dest, Operand);
// If Src is a variable, set the Src and Dest variables to
// prefer each other for register allocation.
if (Variable *Src = llvm::dyn_cast<Variable>(Operand)) {
bool AllowOverlap = false;
Dest->setPreferredRegister(Src, AllowOverlap);
Src->setPreferredRegister(Dest, AllowOverlap);
}
Insts.insert(InsertionPoint, NewInst);
NewInst->updateVars(this);
}
}
}
// Deletes the phi instructions after the loads and stores are placed.
void CfgNode::deletePhis() {
for (PhiList::iterator I = Phis.begin(), E = Phis.end(); I != E; ++I) {
(*I)->setDeleted();
}
}
// Drives the target lowering. Passes the current instruction and the
// next non-deleted instruction for target lowering.
void CfgNode::genCode() {
TargetLowering *Target = Func->getTarget();
LoweringContext &Context = Target->getContext();
// Lower only the regular instructions. Defer the Phi instructions.
Context.init(this);
while (!Context.atEnd()) {
InstList::iterator Orig = Context.getCur();
if (llvm::isa<InstRet>(*Orig))
setHasReturn();
Target->lower();
// Ensure target lowering actually moved the cursor.
assert(Context.getCur() != Orig);
}
}
// ======================== Dump routines ======================== //
void CfgNode::emit(Cfg *Func) const {
Func->setCurrentNode(this);
Ostream &Str = Func->getContext()->getStrEmit();
if (Func->getEntryNode() == this) {
Str << Func->getContext()->mangleName(Func->getFunctionName()) << ":\n";
}
Str << getAsmName() << ":\n";
for (PhiList::const_iterator I = Phis.begin(), E = Phis.end(); I != E; ++I) {
InstPhi *Inst = *I;
if (Inst->isDeleted())
continue;
// Emitting a Phi instruction should cause an error.
Inst->emit(Func);
}
for (InstList::const_iterator I = Insts.begin(), E = Insts.end(); I != E;
++I) {
Inst *Inst = *I;
if (Inst->isDeleted())
continue;
// Here we detect redundant assignments like "mov eax, eax" and
// suppress them.
if (Inst->isRedundantAssign())
continue;
(*I)->emit(Func);
}
}
void CfgNode::dump(Cfg *Func) const {
Func->setCurrentNode(this);
Ostream &Str = Func->getContext()->getStrDump();
......
......@@ -29,6 +29,14 @@ public:
// Access the label number and name for this node.
SizeT getIndex() const { return Number; }
IceString getName() const;
IceString getAsmName() const {
return ".L" + Func->getFunctionName() + "$" + getName();
}
// The HasReturn flag indicates that this node contains a return
// instruction and therefore needs an epilog.
void setHasReturn() { HasReturn = true; }
bool getHasReturn() const { return HasReturn; }
// Access predecessor and successor edge lists.
const NodeList &getInEdges() const { return InEdges; }
......@@ -42,6 +50,11 @@ public:
// node's successors.
void computePredecessors();
void placePhiLoads();
void placePhiStores();
void deletePhis();
void genCode();
void emit(Cfg *Func) const;
void dump(Cfg *Func) const;
private:
......@@ -51,6 +64,7 @@ private:
Cfg *const Func;
const SizeT Number; // label index
IceString Name; // for dumping only
bool HasReturn; // does this block need an epilog?
NodeList InEdges; // in no particular order
NodeList OutEdges; // in no particular order
PhiList Phis; // unordered set of phi instructions
......
......@@ -35,16 +35,23 @@
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Timer.h"
// Roll our own static_assert<> in the absence of C++11. TODO: change
// to static_assert<> with C++11.
template <bool> struct staticAssert;
template <> struct staticAssert<true> {}; // only true is defined
#define STATIC_ASSERT(x) staticAssert<(x)>()
namespace Ice {
class Cfg;
class CfgNode;
class Constant;
class GlobalContext;
class Cfg;
class Inst;
class InstPhi;
class InstTarget;
class Operand;
class TargetLowering;
class Variable;
// TODO: Switch over to LLVM's ADT container classes.
......
......@@ -17,6 +17,7 @@
#include "IceCfg.h"
#include "IceGlobalContext.h"
#include "IceOperand.h"
#include "IceTargetLowering.h"
namespace Ice {
......@@ -75,18 +76,17 @@ public:
GlobalContext::GlobalContext(llvm::raw_ostream *OsDump,
llvm::raw_ostream *OsEmit, VerboseMask Mask,
TargetArch Arch, OptLevel Opt,
IceString TestPrefix)
: StrDump(OsDump), StrEmit(OsEmit), VMask(Mask),
ConstPool(new ConstantPool()), TestPrefix(TestPrefix) {}
ConstPool(new ConstantPool()), Arch(Arch), Opt(Opt),
TestPrefix(TestPrefix), HasEmittedFirstMethod(false) {}
// In this context, name mangling means to rewrite a symbol using a
// given prefix. For a C++ symbol, nest the original symbol inside
// the "prefix" namespace. For other symbols, just prepend the
// prefix.
IceString GlobalContext::mangleName(const IceString &Name) const {
// TODO: Add explicit tests (beyond the implicit tests in the linker
// that come from the cross tests).
//
// An already-nested name like foo::bar() gets pushed down one
// level, making it equivalent to Prefix::foo::bar().
// _ZN3foo3barExyz ==> _ZN6Prefix3foo3barExyz
......@@ -100,9 +100,9 @@ IceString GlobalContext::mangleName(const IceString &Name) const {
unsigned PrefixLength = getTestPrefix().length();
char NameBase[1 + Name.length()];
const size_t BufLen = 30 + Name.length() + getTestPrefix().length();
const size_t BufLen = 30 + Name.length() + PrefixLength;
char NewName[BufLen];
uint32_t BaseLength = 0;
uint32_t BaseLength = 0; // using uint32_t due to sscanf format string
int ItemsParsed = sscanf(Name.c_str(), "_ZN%s", NameBase);
if (ItemsParsed == 1) {
......@@ -118,15 +118,28 @@ IceString GlobalContext::mangleName(const IceString &Name) const {
}
ItemsParsed = sscanf(Name.c_str(), "_Z%u%s", &BaseLength, NameBase);
if (ItemsParsed == 2) {
// Transform _Z3barxyz ==> ZN6Prefix3barExyz
// ^^^^^^^^ ^
if (ItemsParsed == 2 && BaseLength <= strlen(NameBase)) {
// Transform _Z3barxyz ==> _ZN6Prefix3barExyz
// ^^^^^^^^ ^
// (splice in "N6Prefix", and insert "E" after "3bar")
// But an "I" after the identifier indicates a template argument
// list terminated with "E"; insert the new "E" before/after the
// old "E". E.g.:
// Transform _Z3barIabcExyz ==> _ZN6Prefix3barIabcEExyz
// ^^^^^^^^ ^
// (splice in "N6Prefix", and insert "E" after "3barIabcE")
char OrigName[Name.length()];
char OrigSuffix[Name.length()];
strncpy(OrigName, NameBase, BaseLength);
OrigName[BaseLength] = '\0';
strcpy(OrigSuffix, NameBase + BaseLength);
uint32_t ActualBaseLength = BaseLength;
if (NameBase[ActualBaseLength] == 'I') {
++ActualBaseLength;
while (NameBase[ActualBaseLength] != 'E' &&
NameBase[ActualBaseLength] != '\0')
++ActualBaseLength;
}
strncpy(OrigName, NameBase, ActualBaseLength);
OrigName[ActualBaseLength] = '\0';
strcpy(OrigSuffix, NameBase + ActualBaseLength);
snprintf(NewName, BufLen, "_ZN%u%s%u%sE%s", PrefixLength,
getTestPrefix().c_str(), BaseLength, OrigName, OrigSuffix);
return NewName;
......
......@@ -30,7 +30,8 @@ namespace Ice {
class GlobalContext {
public:
GlobalContext(llvm::raw_ostream *OsDump, llvm::raw_ostream *OsEmit,
VerboseMask Mask, IceString TestPrefix);
VerboseMask Mask, TargetArch Arch, OptLevel Opt,
IceString TestPrefix);
~GlobalContext();
// Returns true if any of the specified options in the verbose mask
......@@ -48,6 +49,9 @@ public:
Ostream &getStrDump() { return StrDump; }
Ostream &getStrEmit() { return StrEmit; }
TargetArch getTargetArch() const { return Arch; }
OptLevel getOptLevel() const { return Opt; }
// When emitting assembly, we allow a string to be prepended to
// names of translated functions. This makes it easier to create an
// execution test against a reference translator like llc, with both
......@@ -55,6 +59,15 @@ public:
IceString getTestPrefix() const { return TestPrefix; }
IceString mangleName(const IceString &Name) const;
// The purpose of HasEmitted is to add a header comment at the
// beginning of assembly code emission, doing it once per file
// rather than once per function.
bool testAndSetHasEmittedFirstMethod() {
bool HasEmitted = HasEmittedFirstMethod;
HasEmittedFirstMethod = true;
return HasEmitted;
}
// Manage Constants.
// getConstant*() functions are not const because they might add
// something to the constant pool.
......@@ -75,7 +88,10 @@ private:
llvm::BumpPtrAllocator Allocator;
VerboseMask VMask;
llvm::OwningPtr<class ConstantPool> ConstPool;
const TargetArch Arch;
const OptLevel Opt;
const IceString TestPrefix;
bool HasEmittedFirstMethod;
GlobalContext(const GlobalContext &) LLVM_DELETED_FUNCTION;
GlobalContext &operator=(const GlobalContext &) LLVM_DELETED_FUNCTION;
};
......
......@@ -22,7 +22,7 @@ namespace Ice {
namespace {
// Using non-anonymous struct so that array_lengthof works.
const struct _InstArithmeticAttributes {
const struct InstArithmeticAttributes_ {
const char *DisplayString;
bool IsCommutative;
} InstArithmeticAttributes[] = {
......@@ -36,7 +36,7 @@ const size_t InstArithmeticAttributesSize =
llvm::array_lengthof(InstArithmeticAttributes);
// Using non-anonymous struct so that array_lengthof works.
const struct _InstCastAttributes {
const struct InstCastAttributes_ {
const char *DisplayString;
} InstCastAttributes[] = {
#define X(tag, str) \
......@@ -48,7 +48,7 @@ const struct _InstCastAttributes {
const size_t InstCastAttributesSize = llvm::array_lengthof(InstCastAttributes);
// Using non-anonymous struct so that array_lengthof works.
const struct _InstFcmpAttributes {
const struct InstFcmpAttributes_ {
const char *DisplayString;
} InstFcmpAttributes[] = {
#define X(tag, str) \
......@@ -60,7 +60,7 @@ const struct _InstFcmpAttributes {
const size_t InstFcmpAttributesSize = llvm::array_lengthof(InstFcmpAttributes);
// Using non-anonymous struct so that array_lengthof works.
const struct _InstIcmpAttributes {
const struct InstIcmpAttributes_ {
const char *DisplayString;
} InstIcmpAttributes[] = {
#define X(tag, str) \
......@@ -180,6 +180,35 @@ void InstPhi::addArgument(Operand *Source, CfgNode *Label) {
addSource(Source);
}
// Find the source operand corresponding to the incoming edge for the
// given node. TODO: This uses a linear-time search, which could be
// improved if it becomes a problem.
Operand *InstPhi::getOperandForTarget(CfgNode *Target) const {
for (SizeT I = 0; I < getSrcSize(); ++I) {
if (Labels[I] == Target)
return getSrc(I);
}
llvm_unreachable("Phi target not found");
return NULL;
}
// Change "a=phi(...)" to "a_phi=phi(...)" and return a new
// instruction "a=a_phi".
Inst *InstPhi::lower(Cfg *Func, CfgNode *Node) {
Variable *Dest = getDest();
assert(Dest);
IceString PhiName = Dest->getName() + "_phi";
Variable *NewSrc = Func->makeVariable(Dest->getType(), Node, PhiName);
this->Dest = NewSrc;
InstAssign *NewInst = InstAssign::create(Func, Dest, NewSrc);
// Set Dest and NewSrc to have affinity with each other, as a hint
// for register allocation.
Dest->setPreferredRegister(NewSrc, false);
NewSrc->setPreferredRegister(Dest, false);
Dest->replaceDefinition(NewInst, Node);
return NewInst;
}
InstRet::InstRet(Cfg *Func, Operand *RetValue)
: Inst(Func, Ret, RetValue ? 1 : 0, NULL) {
if (RetValue)
......@@ -233,11 +262,35 @@ NodeList InstSwitch::getTerminatorEdges() const {
InstUnreachable::InstUnreachable(Cfg *Func)
: Inst(Func, Inst::Unreachable, 0, NULL) {}
InstFakeDef::InstFakeDef(Cfg *Func, Variable *Dest, Variable *Src)
: Inst(Func, Inst::FakeDef, Src ? 1 : 0, Dest) {
assert(Dest);
if (Src)
addSource(Src);
}
InstFakeUse::InstFakeUse(Cfg *Func, Variable *Src)
: Inst(Func, Inst::FakeUse, 1, NULL) {
assert(Src);
addSource(Src);
}
InstFakeKill::InstFakeKill(Cfg *Func, const VarList &KilledRegs,
const Inst *Linked)
: Inst(Func, Inst::FakeKill, KilledRegs.size(), NULL), Linked(Linked) {
for (VarList::const_iterator I = KilledRegs.begin(), E = KilledRegs.end();
I != E; ++I) {
Variable *Var = *I;
addSource(Var);
}
}
// ======================== Dump routines ======================== //
void Inst::dumpDecorated(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
if (!Func->getContext()->isVerbose(IceV_Deleted) && isDeleted())
if (!Func->getContext()->isVerbose(IceV_Deleted) &&
(isDeleted() || isRedundantAssign()))
return;
if (Func->getContext()->isVerbose(IceV_InstNumbers)) {
char buf[30];
......@@ -255,6 +308,10 @@ void Inst::dumpDecorated(const Cfg *Func) const {
Str << "\n";
}
void Inst::emit(const Cfg * /*Func*/) const {
llvm_unreachable("emit() called on a non-lowered instruction");
}
void Inst::dump(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
dumpDest(Func);
......@@ -271,6 +328,15 @@ void Inst::dumpSources(const Cfg *Func) const {
}
}
void Inst::emitSources(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrEmit();
for (SizeT I = 0; I < getSrcSize(); ++I) {
if (I > 0)
Str << ", ";
getSrc(I)->emit(Func);
}
}
void Inst::dumpDest(const Cfg *Func) const {
if (getDest())
getDest()->dump(Func);
......@@ -406,7 +472,7 @@ void InstPhi::dump(const Cfg *Func) const {
void InstRet::dump(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
Type Ty = hasRetValue() ? getSrc(0)->getType() : IceType_void;
Type Ty = hasRetValue() ? getRetValue()->getType() : IceType_void;
Str << "ret " << Ty;
if (hasRetValue()) {
Str << " ";
......@@ -433,4 +499,58 @@ void InstUnreachable::dump(const Cfg *Func) const {
Str << "unreachable";
}
void InstFakeDef::emit(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrEmit();
Str << "\t# ";
getDest()->emit(Func);
Str << " = def.pseudo ";
emitSources(Func);
Str << "\n";
}
void InstFakeDef::dump(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
dumpDest(Func);
Str << " = def.pseudo ";
dumpSources(Func);
}
void InstFakeUse::emit(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrEmit();
Str << "\t# ";
Str << "use.pseudo ";
emitSources(Func);
Str << "\n";
}
void InstFakeUse::dump(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
Str << "use.pseudo ";
dumpSources(Func);
}
void InstFakeKill::emit(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrEmit();
Str << "\t# ";
if (Linked->isDeleted())
Str << "// ";
Str << "kill.pseudo ";
emitSources(Func);
Str << "\n";
}
void InstFakeKill::dump(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
if (Linked->isDeleted())
Str << "// ";
Str << "kill.pseudo ";
dumpSources(Func);
}
void InstTarget::dump(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
Str << "[TARGET] ";
Inst::dump(Func);
}
} // end of namespace Ice
......@@ -85,5 +85,4 @@
X(Sle, "sle")
//#define X(tag, str)
#endif // SUBZERO_SRC_ICEINST_DEF
......@@ -47,7 +47,12 @@ public:
Ret,
Select,
Store,
Switch
Switch,
FakeDef, // not part of LLVM/PNaCl bitcode
FakeUse, // not part of LLVM/PNaCl bitcode
FakeKill, // not part of LLVM/PNaCl bitcode
Target // target-specific low-level ICE
// Anything >= Target is an InstTarget subclass.
};
InstKind getKind() const { return Kind; }
......@@ -83,10 +88,13 @@ public:
// basic blocks, i.e. used in a different block from their definition.
void updateVars(CfgNode *Node);
virtual void emit(const Cfg *Func) const;
virtual void dump(const Cfg *Func) const;
void dumpDecorated(const Cfg *Func) const;
void emitSources(const Cfg *Func) const;
void dumpSources(const Cfg *Func) const;
void dumpDest(const Cfg *Func) const;
virtual bool isRedundantAssign() const { return false; }
virtual ~Inst() {}
......@@ -154,6 +162,7 @@ public:
ICEINSTARITHMETIC_TABLE
#undef X
};
static InstArithmetic *create(Cfg *Func, OpKind Op, Variable *Dest,
Operand *Source1, Operand *Source2) {
return new (Func->allocateInst<InstArithmetic>())
......@@ -279,6 +288,7 @@ public:
ICEINSTCAST_TABLE
#undef X
};
static InstCast *create(Cfg *Func, OpKind CastKind, Variable *Dest,
Operand *Source) {
return new (Func->allocateInst<InstCast>())
......@@ -305,6 +315,7 @@ public:
ICEINSTFCMP_TABLE
#undef X
};
static InstFcmp *create(Cfg *Func, FCond Condition, Variable *Dest,
Operand *Source1, Operand *Source2) {
return new (Func->allocateInst<InstFcmp>())
......@@ -332,6 +343,7 @@ public:
ICEINSTICMP_TABLE
#undef X
};
static InstIcmp *create(Cfg *Func, ICond Condition, Variable *Dest,
Operand *Source1, Operand *Source2) {
return new (Func->allocateInst<InstIcmp>())
......@@ -376,6 +388,8 @@ public:
return new (Func->allocateInst<InstPhi>()) InstPhi(Func, MaxSrcs, Dest);
}
void addArgument(Operand *Source, CfgNode *Label);
Operand *getOperandForTarget(CfgNode *Target) const;
Inst *lower(Cfg *Func, CfgNode *Node);
virtual void dump(const Cfg *Func) const;
static bool classof(const Inst *Inst) { return Inst->getKind() == Phi; }
......@@ -522,6 +536,104 @@ private:
virtual ~InstUnreachable() {}
};
// FakeDef instruction. This creates a fake definition of a variable,
// which is how we represent the case when an instruction produces
// multiple results. This doesn't happen with high-level ICE
// instructions, but might with lowered instructions. For example,
// this would be a way to represent condition flags being modified by
// an instruction.
//
// It's generally useful to set the optional source operand to be the
// dest variable of the instruction that actually produces the FakeDef
// dest. Otherwise, the original instruction could be dead-code
// eliminated if its dest operand is unused, and therefore the FakeDef
// dest wouldn't be properly initialized.
class InstFakeDef : public Inst {
public:
static InstFakeDef *create(Cfg *Func, Variable *Dest, Variable *Src = NULL) {
return new (Func->allocateInst<InstFakeDef>()) InstFakeDef(Func, Dest, Src);
}
virtual void emit(const Cfg *Func) const;
virtual void dump(const Cfg *Func) const;
static bool classof(const Inst *Inst) { return Inst->getKind() == FakeDef; }
private:
InstFakeDef(Cfg *Func, Variable *Dest, Variable *Src);
InstFakeDef(const InstFakeDef &) LLVM_DELETED_FUNCTION;
InstFakeDef &operator=(const InstFakeDef &) LLVM_DELETED_FUNCTION;
virtual ~InstFakeDef() {}
};
// FakeUse instruction. This creates a fake use of a variable, to
// keep the instruction that produces that variable from being
// dead-code eliminated. This is useful in a variety of lowering
// situations. The FakeUse instruction has no dest, so it can itself
// never be dead-code eliminated.
class InstFakeUse : public Inst {
public:
static InstFakeUse *create(Cfg *Func, Variable *Src) {
return new (Func->allocateInst<InstFakeUse>()) InstFakeUse(Func, Src);
}
virtual void emit(const Cfg *Func) const;
virtual void dump(const Cfg *Func) const;
static bool classof(const Inst *Inst) { return Inst->getKind() == FakeUse; }
private:
InstFakeUse(Cfg *Func, Variable *Src);
InstFakeUse(const InstFakeUse &) LLVM_DELETED_FUNCTION;
InstFakeUse &operator=(const InstFakeUse &) LLVM_DELETED_FUNCTION;
virtual ~InstFakeUse() {}
};
// FakeKill instruction. This "kills" a set of variables by adding a
// trivial live range at this instruction to each variable. The
// primary use is to indicate that scratch registers are killed after
// a call, so that the register allocator won't assign a scratch
// register to a variable whose live range spans a call.
//
// The FakeKill instruction also holds a pointer to the instruction
// that kills the set of variables, so that if that linked instruction
// gets dead-code eliminated, the FakeKill instruction will as well.
class InstFakeKill : public Inst {
public:
static InstFakeKill *create(Cfg *Func, const VarList &KilledRegs,
const Inst *Linked) {
return new (Func->allocateInst<InstFakeKill>())
InstFakeKill(Func, KilledRegs, Linked);
}
const Inst *getLinked() const { return Linked; }
virtual void emit(const Cfg *Func) const;
virtual void dump(const Cfg *Func) const;
static bool classof(const Inst *Inst) { return Inst->getKind() == FakeKill; }
private:
InstFakeKill(Cfg *Func, const VarList &KilledRegs, const Inst *Linked);
InstFakeKill(const InstFakeKill &) LLVM_DELETED_FUNCTION;
InstFakeKill &operator=(const InstFakeKill &) LLVM_DELETED_FUNCTION;
virtual ~InstFakeKill() {}
// This instruction is ignored if Linked->isDeleted() is true.
const Inst *Linked;
};
// The Target instruction is the base class for all target-specific
// instructions.
class InstTarget : public Inst {
public:
virtual void emit(const Cfg *Func) const = 0;
virtual void dump(const Cfg *Func) const;
static bool classof(const Inst *Inst) { return Inst->getKind() >= Target; }
protected:
InstTarget(Cfg *Func, InstKind Kind, SizeT MaxSrcs, Variable *Dest)
: Inst(Func, Kind, MaxSrcs, Dest) {
assert(Kind >= Target);
}
InstTarget(const InstTarget &) LLVM_DELETED_FUNCTION;
InstTarget &operator=(const InstTarget &) LLVM_DELETED_FUNCTION;
virtual ~InstTarget() {}
};
} // end of namespace Ice
#endif // SUBZERO_SRC_ICEINST_H
//===- subzero/src/IceInstX8632.def - X-macros for x86-32 insts -*- C++ -*-===//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines properties of lowered x86-32 instructions in the
// form of x-macros.
//
//===----------------------------------------------------------------------===//
#ifndef SUBZERO_SRC_ICEINSTX8632_DEF
#define SUBZERO_SRC_ICEINSTX8632_DEF
#define REGX8632_TABLE \
/* val, init, name, name16, name8, scratch, preserved, stackptr, \
frameptr, isI8, isInt, isFP */ \
X(Reg_eax, = 0, "eax", "ax", "al", 1, 0, 0, 0, 1, 1, 0) \
X(Reg_ecx, = Reg_eax + 1, "ecx", "cx", "cl", 1, 0, 0, 0, 1, 1, 0) \
X(Reg_edx, = Reg_eax + 2, "edx", "dx", "dl", 1, 0, 0, 0, 1, 1, 0) \
X(Reg_ebx, = Reg_eax + 3, "ebx", "bx", "bl", 0, 1, 0, 0, 1, 1, 0) \
X(Reg_esp, = Reg_eax + 4, "esp", "sp", , 0, 0, 1, 0, 0, 1, 0) \
X(Reg_ebp, = Reg_eax + 5, "ebp", "bp", , 0, 1, 0, 1, 0, 1, 0) \
X(Reg_esi, = Reg_eax + 6, "esi", "si", , 0, 1, 0, 0, 0, 1, 0) \
X(Reg_edi, = Reg_eax + 7, "edi", "di", , 0, 1, 0, 0, 0, 1, 0) \
X(Reg_ah, /*none*/, "???", , "ah", 0, 0, 0, 0, 1, 0, 0) \
X(Reg_xmm0, /*none*/, "xmm0", , , 1, 0, 0, 0, 0, 0, 1) \
X(Reg_xmm1, = Reg_xmm0 + 1, "xmm1", , , 1, 0, 0, 0, 0, 0, 1) \
X(Reg_xmm2, = Reg_xmm0 + 2, "xmm2", , , 1, 0, 0, 0, 0, 0, 1) \
X(Reg_xmm3, = Reg_xmm0 + 3, "xmm3", , , 1, 0, 0, 0, 0, 0, 1) \
X(Reg_xmm4, = Reg_xmm0 + 4, "xmm4", , , 1, 0, 0, 0, 0, 0, 1) \
X(Reg_xmm5, = Reg_xmm0 + 5, "xmm5", , , 1, 0, 0, 0, 0, 0, 1) \
X(Reg_xmm6, = Reg_xmm0 + 6, "xmm6", , , 1, 0, 0, 0, 0, 0, 1) \
X(Reg_xmm7, = Reg_xmm0 + 7, "xmm7", , , 1, 0, 0, 0, 0, 0, 1) \
//#define X(val, init, name, name16, name8, scratch, preserved, stackptr,
// frameptr, isI8, isInt, isFP)
#define ICEINSTX8632BR_TABLE \
/* enum value, dump, emit */ \
X(Br_a, "a", "ja") \
X(Br_ae, "ae", "jae") \
X(Br_b, "b", "jb") \
X(Br_be, "be", "jbe") \
X(Br_e, "e", "je") \
X(Br_g, "g", "jg") \
X(Br_ge, "ge", "jge") \
X(Br_l, "l", "jl") \
X(Br_le, "le", "jle") \
X(Br_ne, "ne", "jne") \
X(Br_np, "np", "jnp") \
X(Br_p, "p", "jp") \
//#define X(tag, dump, emit)
#define ICETYPEX8632_TABLE \
/* tag, cvt, sdss, width */ \
X(IceType_void, "?", , "???") \
X(IceType_i1, "i", , "byte ptr") \
X(IceType_i8, "i", , "byte ptr") \
X(IceType_i16, "i", , "word ptr") \
X(IceType_i32, "i", , "dword ptr") \
X(IceType_i64, "i", , "qword ptr") \
X(IceType_f32, "s", "ss", "dword ptr") \
X(IceType_f64, "d", "sd", "qword ptr") \
X(IceType_NUM, "?", , "???") \
//#define X(tag, cvt, sdss, width)
#endif // SUBZERO_SRC_ICEINSTX8632_DEF
......@@ -16,6 +16,7 @@
#include "IceCfg.h"
#include "IceInst.h"
#include "IceOperand.h"
#include "IceTargetLowering.h" // dumping stack/frame pointer register
namespace Ice {
......@@ -27,6 +28,14 @@ bool operator<(const RelocatableTuple &A, const RelocatableTuple &B) {
return A.Name < B.Name;
}
bool operator<(const RegWeight &A, const RegWeight &B) {
return A.getWeight() < B.getWeight();
}
bool operator<=(const RegWeight &A, const RegWeight &B) { return !(B < A); }
bool operator==(const RegWeight &A, const RegWeight &B) {
return !(B < A) && !(A < B);
}
void Variable::setUse(const Inst *Inst, const CfgNode *Node) {
if (DefNode == NULL)
return;
......@@ -66,19 +75,57 @@ IceString Variable::getName() const {
return buf;
}
Variable Variable::asType(Type Ty) {
Variable V(Ty, DefNode, Number, Name);
V.RegNum = RegNum;
V.StackOffset = StackOffset;
return V;
}
// ======================== dump routines ======================== //
void Variable::emit(const Cfg *Func) const {
Func->getTarget()->emitVariable(this, Func);
}
void Variable::dump(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
const CfgNode *CurrentNode = Func->getCurrentNode();
(void)CurrentNode; // used only in assert()
assert(CurrentNode == NULL || DefNode == NULL || DefNode == CurrentNode);
Str << "%" << getName();
if (Func->getContext()->isVerbose(IceV_RegOrigins) ||
(!hasReg() && !Func->getTarget()->hasComputedFrame()))
Str << "%" << getName();
if (hasReg()) {
if (Func->getContext()->isVerbose(IceV_RegOrigins))
Str << ":";
Str << Func->getTarget()->getRegName(RegNum, getType());
} else if (Func->getTarget()->hasComputedFrame()) {
if (Func->getContext()->isVerbose(IceV_RegOrigins))
Str << ":";
Str << "[" << Func->getTarget()->getRegName(
Func->getTarget()->getFrameOrStackReg(), IceType_i32);
int32_t Offset = getStackOffset();
if (Offset) {
if (Offset > 0)
Str << "+";
Str << Offset;
}
Str << "]";
}
}
void Operand::dump(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
Str << "Operand<?>";
void ConstantRelocatable::emit(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrEmit();
if (SuppressMangling)
Str << Name;
else
Str << Func->getContext()->mangleName(Name);
if (Offset) {
if (Offset > 0)
Str << "+";
Str << Offset;
}
}
void ConstantRelocatable::dump(const Cfg *Func) const {
......@@ -88,4 +135,12 @@ void ConstantRelocatable::dump(const Cfg *Func) const {
Str << "+" << Offset;
}
Ostream &operator<<(Ostream &Str, const RegWeight &W) {
if (W.getWeight() == RegWeight::Inf)
Str << "Inf";
else
Str << W.getWeight();
return Str;
}
} // end of namespace Ice
......@@ -49,6 +49,7 @@ public:
assert(I < getNumVars());
return Vars[I];
}
virtual void emit(const Cfg *Func) const = 0;
virtual void dump(const Cfg *Func) const = 0;
// Query whether this object was allocated in isolation, or added to
......@@ -79,6 +80,7 @@ private:
// constants are allocated from a global arena and are pooled.
class Constant : public Operand {
public:
virtual void emit(const Cfg *Func) const = 0;
virtual void dump(const Cfg *Func) const = 0;
static bool classof(const Operand *Operand) {
......@@ -107,6 +109,10 @@ public:
ConstantPrimitive(Ty, Value);
}
T getValue() const { return Value; }
virtual void emit(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrEmit();
Str << getValue();
}
virtual void dump(const Cfg *Func) const {
Ostream &Str = Func->getContext()->getStrDump();
Str << getValue();
......@@ -163,6 +169,7 @@ public:
IceString getName() const { return Name; }
void setSuppressMangling(bool Value) { SuppressMangling = Value; }
bool getSuppressMangling() const { return SuppressMangling; }
virtual void emit(const Cfg *Func) const;
virtual void dump(const Cfg *Func) const;
static bool classof(const Operand *Operand) {
......@@ -184,6 +191,34 @@ private:
bool SuppressMangling;
};
// RegWeight is a wrapper for a uint32_t weight value, with a
// special value that represents infinite weight, and an addWeight()
// method that ensures that W+infinity=infinity.
class RegWeight {
public:
RegWeight() : Weight(0) {}
RegWeight(uint32_t Weight) : Weight(Weight) {}
const static uint32_t Inf = ~0; // Force regalloc to give a register
const static uint32_t Zero = 0; // Force regalloc NOT to give a register
void addWeight(uint32_t Delta) {
if (Delta == Inf)
Weight = Inf;
else if (Weight != Inf)
Weight += Delta;
}
void addWeight(const RegWeight &Other) { addWeight(Other.Weight); }
void setWeight(uint32_t Val) { Weight = Val; }
uint32_t getWeight() const { return Weight; }
bool isInf() const { return Weight == Inf; }
private:
uint32_t Weight;
};
Ostream &operator<<(Ostream &Str, const RegWeight &W);
bool operator<(const RegWeight &A, const RegWeight &B);
bool operator<=(const RegWeight &A, const RegWeight &B);
bool operator==(const RegWeight &A, const RegWeight &B);
// Variable represents an operand that is register-allocated or
// stack-allocated. If it is register-allocated, it will ultimately
// have a non-negative RegNum field.
......@@ -208,23 +243,66 @@ public:
bool getIsArg() const { return IsArgument; }
void setIsArg(Cfg *Func);
int32_t getStackOffset() const { return StackOffset; }
void setStackOffset(int32_t Offset) { StackOffset = Offset; }
static const int32_t NoRegister = -1;
bool hasReg() const { return getRegNum() != NoRegister; }
int32_t getRegNum() const { return RegNum; }
void setRegNum(int32_t NewRegNum) {
// Regnum shouldn't be set more than once.
assert(!hasReg() || RegNum == NewRegNum);
RegNum = NewRegNum;
}
RegWeight getWeight() const { return Weight; }
void setWeight(uint32_t NewWeight) { Weight = NewWeight; }
void setWeightInfinite() { Weight = RegWeight::Inf; }
Variable *getPreferredRegister() const { return RegisterPreference; }
bool getRegisterOverlap() const { return AllowRegisterOverlap; }
void setPreferredRegister(Variable *Prefer, bool Overlap) {
RegisterPreference = Prefer;
AllowRegisterOverlap = Overlap;
}
Variable *getLo() const { return LoVar; }
Variable *getHi() const { return HiVar; }
void setLoHi(Variable *Lo, Variable *Hi) {
assert(LoVar == NULL);
assert(HiVar == NULL);
LoVar = Lo;
HiVar = Hi;
}
// Creates a temporary copy of the variable with a different type.
// Used primarily for syntactic correctness of textual assembly
// emission. Note that only basic information is copied, in
// particular not DefInst, IsArgument, Weight, RegisterPreference,
// AllowRegisterOverlap, LoVar, HiVar, VarsReal.
Variable asType(Type Ty);
virtual void emit(const Cfg *Func) const;
virtual void dump(const Cfg *Func) const;
static bool classof(const Operand *Operand) {
return Operand->getKind() == kVariable;
}
// The destructor is public because of the asType() method.
virtual ~Variable() {}
private:
Variable(Type Ty, const CfgNode *Node, SizeT Index, const IceString &Name)
: Operand(kVariable, Ty), Number(Index), Name(Name), DefInst(NULL),
DefNode(Node), IsArgument(false) {
DefNode(Node), IsArgument(false), StackOffset(0), RegNum(NoRegister),
Weight(1), RegisterPreference(NULL), AllowRegisterOverlap(false),
LoVar(NULL), HiVar(NULL) {
Vars = VarsReal;
Vars[0] = this;
NumVars = 1;
}
Variable(const Variable &) LLVM_DELETED_FUNCTION;
Variable &operator=(const Variable &) LLVM_DELETED_FUNCTION;
virtual ~Variable() {}
// Number is unique across all variables, and is used as a
// (bit)vector index for liveness analysis.
const SizeT Number;
......@@ -241,6 +319,32 @@ private:
// of incrementally computing and maintaining the information.
const CfgNode *DefNode;
bool IsArgument;
// StackOffset is the canonical location on stack (only if
// RegNum<0 || IsArgument).
int32_t StackOffset;
// RegNum is the allocated register, or NoRegister if it isn't
// register-allocated.
int32_t RegNum;
RegWeight Weight; // Register allocation priority
// RegisterPreference says that if possible, the register allocator
// should prefer the register that was assigned to this linked
// variable. It also allows a spill slot to share its stack
// location with another variable, if that variable does not get
// register-allocated and therefore has a stack location.
Variable *RegisterPreference;
// AllowRegisterOverlap says that it is OK to honor
// RegisterPreference and "share" a register even if the two live
// ranges overlap.
bool AllowRegisterOverlap;
// LoVar and HiVar are needed for lowering from 64 to 32 bits. When
// lowering from I64 to I32 on a 32-bit architecture, we split the
// variable into two machine-size pieces. LoVar is the low-order
// machine-size portion, and HiVar is the remaining high-order
// portion. TODO: It's wasteful to penalize all variables on all
// targets this way; use a sparser representation. It's also
// wasteful for a 64-bit target.
Variable *LoVar;
Variable *HiVar;
// VarsReal (and Operand::Vars) are set up such that Vars[0] ==
// this.
Variable *VarsReal[1];
......
//===- subzero/src/IceTargetLowering.cpp - Basic lowering implementation --===//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the skeleton of the TargetLowering class,
// specifically invoking the appropriate lowering method for a given
// instruction kind and driving global register allocation. It also
// implements the non-deleted instruction iteration in
// LoweringContext.
//
//===----------------------------------------------------------------------===//
#include "IceCfg.h" // setError()
#include "IceCfgNode.h"
#include "IceOperand.h"
#include "IceTargetLowering.h"
#include "IceTargetLoweringX8632.h"
namespace Ice {
void LoweringContext::init(CfgNode *N) {
Node = N;
Cur = getNode()->getInsts().begin();
End = getNode()->getInsts().end();
skipDeleted(Cur);
Next = Cur;
advance(Next);
}
void LoweringContext::insert(Inst *Inst) {
getNode()->getInsts().insert(Next, Inst);
Inst->updateVars(getNode());
}
void LoweringContext::skipDeleted(InstList::iterator &I) {
while (I != End && (*I)->isDeleted())
++I;
}
void LoweringContext::advance(InstList::iterator &I) {
if (I != End) {
++I;
skipDeleted(I);
}
}
TargetLowering *TargetLowering::createLowering(TargetArch Target, Cfg *Func) {
// These statements can be #ifdef'd to specialize the code generator
// to a subset of the available targets. TODO: use CRTP.
if (Target == Target_X8632)
return TargetX8632::create(Func);
#if 0
if (Target == Target_X8664)
return IceTargetX8664::create(Func);
if (Target == Target_ARM32)
return IceTargetARM32::create(Func);
if (Target == Target_ARM64)
return IceTargetARM64::create(Func);
#endif
Func->setError("Unsupported target");
return NULL;
}
// Lowers a single instruction according to the information in
// Context, by checking the Context.Cur instruction kind and calling
// the appropriate lowering method. The lowering method should insert
// target instructions at the Cur.Next insertion point, and should not
// delete the Context.Cur instruction or advance Context.Cur.
//
// The lowering method may look ahead in the instruction stream as
// desired, and lower additional instructions in conjunction with the
// current one, for example fusing a compare and branch. If it does,
// it should advance Context.Cur to point to the next non-deleted
// instruction to process, and it should delete any additional
// instructions it consumes.
void TargetLowering::lower() {
assert(!Context.atEnd());
Inst *Inst = *Context.getCur();
switch (Inst->getKind()) {
case Inst::Alloca:
lowerAlloca(llvm::dyn_cast<InstAlloca>(Inst));
break;
case Inst::Arithmetic:
lowerArithmetic(llvm::dyn_cast<InstArithmetic>(Inst));
break;
case Inst::Assign:
lowerAssign(llvm::dyn_cast<InstAssign>(Inst));
break;
case Inst::Br:
lowerBr(llvm::dyn_cast<InstBr>(Inst));
break;
case Inst::Call:
lowerCall(llvm::dyn_cast<InstCall>(Inst));
break;
case Inst::Cast:
lowerCast(llvm::dyn_cast<InstCast>(Inst));
break;
case Inst::Fcmp:
lowerFcmp(llvm::dyn_cast<InstFcmp>(Inst));
break;
case Inst::Icmp:
lowerIcmp(llvm::dyn_cast<InstIcmp>(Inst));
break;
case Inst::Load:
lowerLoad(llvm::dyn_cast<InstLoad>(Inst));
break;
case Inst::Phi:
lowerPhi(llvm::dyn_cast<InstPhi>(Inst));
break;
case Inst::Ret:
lowerRet(llvm::dyn_cast<InstRet>(Inst));
break;
case Inst::Select:
lowerSelect(llvm::dyn_cast<InstSelect>(Inst));
break;
case Inst::Store:
lowerStore(llvm::dyn_cast<InstStore>(Inst));
break;
case Inst::Switch:
lowerSwitch(llvm::dyn_cast<InstSwitch>(Inst));
break;
case Inst::Unreachable:
lowerUnreachable(llvm::dyn_cast<InstUnreachable>(Inst));
break;
case Inst::FakeDef:
case Inst::FakeUse:
case Inst::FakeKill:
case Inst::Target:
// These are all Target instruction types and shouldn't be
// encountered at this stage.
Func->setError("Can't lower unsupported instruction type");
break;
}
Inst->setDeleted();
postLower();
Context.advanceCur();
Context.advanceNext();
}
} // end of namespace Ice
//===- subzero/src/IceTargetLowering.h - Lowering interface -----*- C++ -*-===//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file declares the TargetLowering and LoweringContext
// classes. TargetLowering is an abstract class used to drive the
// translation/lowering process. LoweringContext maintains a
// context for lowering each instruction, offering conveniences such
// as iterating over non-deleted instructions.
//
//===----------------------------------------------------------------------===//
#ifndef SUBZERO_SRC_ICETARGETLOWERING_H
#define SUBZERO_SRC_ICETARGETLOWERING_H
#include "IceDefs.h"
#include "IceTypes.h"
#include "IceInst.h" // for the names of the Inst subtypes
namespace Ice {
// LoweringContext makes it easy to iterate through non-deleted
// instructions in a node, and insert new (lowered) instructions at
// the current point. Along with the instruction list container and
// associated iterators, it holds the current node, which is needed
// when inserting new instructions in order to track whether variables
// are used as single-block or multi-block.
class LoweringContext {
public:
LoweringContext() : Node(NULL) {}
~LoweringContext() {}
void init(CfgNode *Node);
Inst *getNextInst() const {
if (Next == End)
return NULL;
return *Next;
}
CfgNode *getNode() const { return Node; }
bool atEnd() const { return Cur == End; }
InstList::iterator getCur() const { return Cur; }
InstList::iterator getEnd() const { return End; }
void insert(Inst *Inst);
void advanceCur() { Cur = Next; }
void advanceNext() { advance(Next); }
void setInsertPoint(const InstList::iterator &Position) { Next = Position; }
private:
// Node is the argument to Inst::updateVars().
CfgNode *Node;
// Cur points to the current instruction being considered. It is
// guaranteed to point to a non-deleted instruction, or to be End.
InstList::iterator Cur;
// Next doubles as a pointer to the next valid instruction (if any),
// and the new-instruction insertion point. It is also updated for
// the caller in case the lowering consumes more than one high-level
// instruction. It is guaranteed to point to a non-deleted
// instruction after Cur, or to be End. TODO: Consider separating
// the notion of "next valid instruction" and "new instruction
// insertion point", to avoid confusion when previously-deleted
// instructions come between the two points.
InstList::iterator Next;
// End is a copy of Insts.end(), used if Next needs to be advanced.
InstList::iterator End;
void skipDeleted(InstList::iterator &I);
void advance(InstList::iterator &I);
LoweringContext(const LoweringContext &) LLVM_DELETED_FUNCTION;
LoweringContext &operator=(const LoweringContext &) LLVM_DELETED_FUNCTION;
};
class TargetLowering {
public:
static TargetLowering *createLowering(TargetArch Target, Cfg *Func);
void translate() {
switch (Ctx->getOptLevel()) {
case Opt_m1:
translateOm1();
break;
case Opt_0:
translateO0();
break;
case Opt_1:
translateO1();
break;
case Opt_2:
translateO2();
break;
default:
Func->setError("Target doesn't specify lowering steps.");
break;
}
}
virtual void translateOm1() {
Func->setError("Target doesn't specify Om1 lowering steps.");
}
virtual void translateO0() {
Func->setError("Target doesn't specify O0 lowering steps.");
}
virtual void translateO1() {
Func->setError("Target doesn't specify O1 lowering steps.");
}
virtual void translateO2() {
Func->setError("Target doesn't specify O2 lowering steps.");
}
// Lowers a single instruction.
void lower();
// Returns a variable pre-colored to the specified physical
// register. This is generally used to get very direct access to
// the register such as in the prolog or epilog or for marking
// scratch registers as killed by a call.
virtual Variable *getPhysicalRegister(SizeT RegNum) = 0;
// Returns a printable name for the register.
virtual IceString getRegName(SizeT RegNum, Type Ty) const = 0;
virtual bool hasFramePointer() const { return false; }
virtual SizeT getFrameOrStackReg() const = 0;
virtual size_t typeWidthInBytesOnStack(Type Ty) = 0;
bool hasComputedFrame() const { return HasComputedFrame; }
int32_t getStackAdjustment() const { return StackAdjustment; }
void updateStackAdjustment(int32_t Offset) { StackAdjustment += Offset; }
void resetStackAdjustment() { StackAdjustment = 0; }
LoweringContext &getContext() { return Context; }
enum RegSet {
RegSet_None = 0,
RegSet_CallerSave = 1 << 0,
RegSet_CalleeSave = 1 << 1,
RegSet_StackPointer = 1 << 2,
RegSet_FramePointer = 1 << 3,
RegSet_All = ~RegSet_None
};
typedef uint32_t RegSetMask;
virtual llvm::SmallBitVector getRegisterSet(RegSetMask Include,
RegSetMask Exclude) const = 0;
virtual const llvm::SmallBitVector &getRegisterSetForType(Type Ty) const = 0;
void regAlloc();
virtual void emitVariable(const Variable *Var, const Cfg *Func) const = 0;
virtual void addProlog(CfgNode *Node) = 0;
virtual void addEpilog(CfgNode *Node) = 0;
virtual ~TargetLowering() {}
protected:
TargetLowering(Cfg *Func)
: Func(Func), Ctx(Func->getContext()), HasComputedFrame(false),
StackAdjustment(0) {}
virtual void lowerAlloca(const InstAlloca *Inst) = 0;
virtual void lowerArithmetic(const InstArithmetic *Inst) = 0;
virtual void lowerAssign(const InstAssign *Inst) = 0;
virtual void lowerBr(const InstBr *Inst) = 0;
virtual void lowerCall(const InstCall *Inst) = 0;
virtual void lowerCast(const InstCast *Inst) = 0;
virtual void lowerFcmp(const InstFcmp *Inst) = 0;
virtual void lowerIcmp(const InstIcmp *Inst) = 0;
virtual void lowerLoad(const InstLoad *Inst) = 0;
virtual void lowerPhi(const InstPhi *Inst) = 0;
virtual void lowerRet(const InstRet *Inst) = 0;
virtual void lowerSelect(const InstSelect *Inst) = 0;
virtual void lowerStore(const InstStore *Inst) = 0;
virtual void lowerSwitch(const InstSwitch *Inst) = 0;
virtual void lowerUnreachable(const InstUnreachable *Inst) = 0;
// This gives the target an opportunity to post-process the lowered
// expansion before returning. The primary intention is to do some
// Register Manager activity as necessary, specifically to eagerly
// allocate registers based on affinity and other factors. The
// simplest lowering does nothing here and leaves it all to a
// subsequent global register allocation pass.
virtual void postLower() {}
Cfg *Func;
GlobalContext *Ctx;
bool HasComputedFrame;
// StackAdjustment keeps track of the current stack offset from its
// natural location, as arguments are pushed for a function call.
int32_t StackAdjustment;
LoweringContext Context;
private:
TargetLowering(const TargetLowering &) LLVM_DELETED_FUNCTION;
TargetLowering &operator=(const TargetLowering &) LLVM_DELETED_FUNCTION;
};
} // end of namespace Ice
#endif // SUBZERO_SRC_ICETARGETLOWERING_H
//===- subzero/src/IceTargetLoweringX8632.def - x86-32 X-macros -*- C++ -*-===//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines certain patterns for lowering to x86-32 target
// instructions, in the form of x-macros.
//
//===----------------------------------------------------------------------===//
#ifndef SUBZERO_SRC_ICETARGETLOWERINGX8632_DEF
#define SUBZERO_SRC_ICETARGETLOWERINGX8632_DEF
#define FCMPX8632_TABLE \
/* val, dflt, swap, C1, C2 */ \
X(False, 0, 0, Br_None, Br_None) \
X(Oeq, 0, 0, Br_ne, Br_p) \
X(Ogt, 1, 0, Br_a, Br_None) \
X(Oge, 1, 0, Br_ae, Br_None) \
X(Olt, 1, 1, Br_a, Br_None) \
X(Ole, 1, 1, Br_ae, Br_None) \
X(One, 1, 0, Br_ne, Br_None) \
X(Ord, 1, 0, Br_np, Br_None) \
X(Ueq, 1, 0, Br_e, Br_None) \
X(Ugt, 1, 1, Br_b, Br_None) \
X(Uge, 1, 1, Br_be, Br_None) \
X(Ult, 1, 0, Br_b, Br_None) \
X(Ule, 1, 0, Br_be, Br_None) \
X(Une, 1, 0, Br_ne, Br_p) \
X(Uno, 1, 0, Br_p, Br_None) \
X(True, 1, 0, Br_None, Br_None) \
//#define X(val, dflt, swap, C1, C2)
#define ICMPX8632_TABLE \
/* val, C_32, C1_64, C2_64, C3_64 */ \
X(Eq, Br_e, Br_None, Br_None, Br_None) \
X(Ne, Br_ne, Br_None, Br_None, Br_None) \
X(Ugt, Br_a, Br_a, Br_b, Br_a) \
X(Uge, Br_ae, Br_a, Br_b, Br_ae) \
X(Ult, Br_b, Br_b, Br_a, Br_b) \
X(Ule, Br_be, Br_b, Br_a, Br_be) \
X(Sgt, Br_g, Br_g, Br_l, Br_a) \
X(Sge, Br_ge, Br_g, Br_l, Br_ae) \
X(Slt, Br_l, Br_l, Br_g, Br_b) \
X(Sle, Br_le, Br_l, Br_g, Br_be) \
//#define X(val, C_32, C1_64, C2_64, C3_64)
#endif // SUBZERO_SRC_ICETARGETLOWERINGX8632_DEF
......@@ -41,7 +41,7 @@ size_t typeWidthInBytes(Type Ty) {
if (Index < TypeAttributesSize) {
Width = TypeAttributes[Index].TypeWidthInBytes;
} else {
assert(0 && "Invalid type for typeWidthInBytes()");
llvm_unreachable("Invalid type for typeWidthInBytes()");
}
return Width;
}
......@@ -52,7 +52,7 @@ size_t typeAlignInBytes(Type Ty) {
if (Index < TypeAttributesSize) {
Align = TypeAttributes[Index].TypeAlignInBytes;
} else {
assert(0 && "Invalid type for typeAlignInBytes()");
llvm_unreachable("Invalid type for typeAlignInBytes()");
}
return Align;
}
......@@ -65,7 +65,7 @@ template <> Ostream &operator<<(Ostream &Str, const Type &Ty) {
Str << TypeAttributes[Index].DisplayString;
} else {
Str << "???";
assert(0 && "Invalid type for printing");
llvm_unreachable("Invalid type for printing");
}
return Str;
......
......@@ -25,7 +25,8 @@
X(IceType_i32, 4, 1, "i32") \
X(IceType_i64, 8, 1, "i64") \
X(IceType_f32, 4, 4, "float") \
X(IceType_f64, 8, 8, "double")
X(IceType_f64, 8, 8, "double") \
X(IceType_NUM, 0, 0, "<invalid>") \
//#define X(tag, size, align, str)
#endif // SUBZERO_SRC_ICETYPES_DEF
......@@ -26,6 +26,20 @@ enum Type {
#undef X
};
enum TargetArch {
Target_X8632,
Target_X8664,
Target_ARM32,
Target_ARM64
};
enum OptLevel {
Opt_m1,
Opt_0,
Opt_1,
Opt_2
};
size_t typeWidthInBytes(Type Ty);
size_t typeAlignInBytes(Type Ty);
......
......@@ -165,8 +165,9 @@ private:
return Ice::IceType_void;
}
// Given a LLVM instruction and an operand number, produce the Operand this
// refers to. If there's no such operand, return NULL.
// Given an LLVM instruction and an operand number, produce the
// Ice::Operand this refers to. If there's no such operand, return
// NULL.
Ice::Operand *convertOperand(const Instruction *Inst, unsigned OpNum) {
if (OpNum >= Inst->getNumOperands()) {
return NULL;
......@@ -189,10 +190,10 @@ private:
return Ctx->getConstantFloat(CFP->getValueAPF().convertToFloat());
else if (Type == Ice::IceType_f64)
return Ctx->getConstantDouble(CFP->getValueAPF().convertToDouble());
assert(0 && "Unexpected floating point type");
llvm_unreachable("Unexpected floating point type");
return NULL;
} else {
assert(0 && "Unhandled constant type");
llvm_unreachable("Unhandled constant type");
return NULL;
}
} else {
......@@ -534,7 +535,7 @@ private:
return Ice::InstAlloca::create(Func, ByteCount, Align, Dest);
}
Ice::Inst *convertUnreachableInstruction(const UnreachableInst *Inst) {
Ice::Inst *convertUnreachableInstruction(const UnreachableInst * /*Inst*/) {
return Ice::InstUnreachable::create(Func);
}
......@@ -576,12 +577,35 @@ static cl::list<Ice::VerboseItem> VerboseList(
clEnumValN(Ice::IceV_Timing, "time", "Pass timing details"),
clEnumValN(Ice::IceV_All, "all", "Use all verbose options"),
clEnumValN(Ice::IceV_None, "none", "No verbosity"), clEnumValEnd));
static cl::opt<Ice::TargetArch> TargetArch(
"target", cl::desc("Target architecture:"), cl::init(Ice::Target_X8632),
cl::values(
clEnumValN(Ice::Target_X8632, "x8632", "x86-32"),
clEnumValN(Ice::Target_X8632, "x86-32", "x86-32 (same as x8632)"),
clEnumValN(Ice::Target_X8632, "x86_32", "x86-32 (same as x8632)"),
clEnumValN(Ice::Target_X8664, "x8664", "x86-64"),
clEnumValN(Ice::Target_X8664, "x86-64", "x86-64 (same as x8664)"),
clEnumValN(Ice::Target_X8664, "x86_64", "x86-64 (same as x8664)"),
clEnumValN(Ice::Target_ARM32, "arm", "arm32"),
clEnumValN(Ice::Target_ARM32, "arm32", "arm32 (same as arm)"),
clEnumValN(Ice::Target_ARM64, "arm64", "arm64"), clEnumValEnd));
static cl::opt<Ice::OptLevel>
OptLevel(cl::desc("Optimization level"), cl::init(Ice::Opt_m1),
cl::value_desc("level"),
cl::values(clEnumValN(Ice::Opt_m1, "Om1", "-1"),
clEnumValN(Ice::Opt_m1, "O-1", "-1"),
clEnumValN(Ice::Opt_0, "O0", "0"),
clEnumValN(Ice::Opt_1, "O1", "1"),
clEnumValN(Ice::Opt_2, "O2", "2"), clEnumValEnd));
static cl::opt<std::string> IRFilename(cl::Positional, cl::desc("<IR file>"),
cl::init("-"));
static cl::opt<std::string> OutputFilename("o",
cl::desc("Override output filename"),
cl::init("-"),
cl::value_desc("filename"));
static cl::opt<std::string> LogFilename("log", cl::desc("Set log filename"),
cl::init("-"),
cl::value_desc("filename"));
static cl::opt<std::string>
TestPrefix("prefix", cl::desc("Prepend a prefix to symbol names for testing"),
cl::init(""), cl::value_desc("prefix"));
......@@ -605,6 +629,8 @@ InputFileFormat(
cl::init(LLVMFormat));
int main(int argc, char **argv) {
int ExitStatus = 0;
cl::ParseCommandLineOptions(argc, argv);
// Parse the input LLVM IR file into a module.
......@@ -637,8 +663,14 @@ int main(int argc, char **argv) {
raw_os_ostream *Os =
new raw_os_ostream(OutputFilename == "-" ? std::cout : Ofs);
Os->SetUnbuffered();
std::ofstream Lfs;
if (LogFilename != "-") {
Lfs.open(LogFilename.c_str(), std::ofstream::out);
}
raw_os_ostream *Ls = new raw_os_ostream(LogFilename == "-" ? std::cout : Lfs);
Ls->SetUnbuffered();
Ice::GlobalContext Ctx(Os, Os, VMask, TestPrefix);
Ice::GlobalContext Ctx(Ls, Os, VMask, TargetArch, OptLevel, TestPrefix);
for (Module::const_iterator I = Mod->begin(), E = Mod->end(); I != E; ++I) {
if (I->empty())
......@@ -658,8 +690,28 @@ int main(int argc, char **argv) {
if (DisableTranslation) {
Func->dump();
} else {
Ice::Timer TTranslate;
Func->translate();
if (SubzeroTimingEnabled) {
std::cerr << "[Subzero timing] Translate function "
<< Func->getFunctionName() << ": "
<< TTranslate.getElapsedSec() << " sec\n";
}
if (Func->hasError()) {
errs() << "ICE translation error: " << Func->getError() << "\n";
ExitStatus = 1;
}
Ice::Timer TEmit;
Func->emit();
if (SubzeroTimingEnabled) {
std::cerr << "[Subzero timing] Emit function "
<< Func->getFunctionName() << ": " << TEmit.getElapsedSec()
<< " sec\n";
}
}
}
return 0;
return ExitStatus;
}
; RUIN: %llvm2ice --verbose none %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; This is a basic test of the alloca instruction - one test for alloca
; of a fixed size, and one test for variable size.
; RUIN: %llvm2ice -O2 --verbose none %s | FileCheck %s
; RUN: %llvm2ice -Om1 --verbose none %s | FileCheck --check-prefix=OPTM1 %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -10,11 +14,18 @@ entry:
%__2 = ptrtoint i8* %array to i32
call void @f1(i32 %__2)
ret void
; CHECK: sub esp, 400
; CHECK-NEXT: mov eax, esp
; CHECK-NEXT: push eax
; CHECK-NEXT: call f1
}
; CHECK: fixed_400:
; CHECK: sub esp, 400
; CHECK-NEXT: mov eax, esp
; CHECK-NEXT: push eax
; CHECK-NEXT: call f1
;
; OPTM1: fixed_400:
; OPTM1: sub esp, 400
; OPTM1-NEXT: mov {{.*}}, esp
; OPTM1: push
; OPTM1-NEXT: call f1
declare void @f1(i32)
......@@ -24,12 +35,18 @@ entry:
%__2 = ptrtoint i8* %array to i32
call void @f2(i32 %__2)
ret void
; CHECK: mov eax, dword ptr [ebp+8]
; CHECK-NEXT: sub esp, eax
; CHECK-NEXT: mov eax, esp
; CHECK-NEXT: push eax
; CHECK-NEXT: call f2
}
; CHECK: variable_n:
; CHECK: mov eax, dword ptr [ebp+8]
; CHECK-NEXT: sub esp, eax
; CHECK-NEXT: mov eax, esp
; CHECK-NEXT: push eax
; CHECK-NEXT: call f2
;
; OPTM1: variable_n:
; OPTM1: mov {{.*}}, esp
; OPTM1: push
; OPTM1-NEXT: call f2
declare void @f2(i32)
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; This is a very early test that just checks the representation of i32
; arithmetic instructions. No assembly tests are done.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; This is a very early test that just checks the representation of
; arithmetic instructions, i64, variables, and constants. No assembly
; tests are done.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Trivial smoke test of bitcast between integer and FP types.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -7,24 +9,28 @@
define internal i32 @cast_f2i(float %f) {
entry:
%v0 = bitcast float %f to i32
; CHECK: bitcast
ret i32 %v0
}
define internal float @cast_i2f(i32 %i) {
entry:
%v0 = bitcast i32 %i to float
; CHECK: bitcast
ret float %v0
}
define internal i64 @cast_d2ll(double %d) {
entry:
%v0 = bitcast double %d to i64
; CHECK: bitcast
ret i64 %v0
}
define internal double @cast_ll2d(i64 %ll) {
entry:
%v0 = bitcast i64 %ll to double
; CHECK: bitcast
ret double %v0
}
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Trivial smoke test of icmp without fused branch opportunity.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice %s -verbose inst | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Trivial smoke test of compare and branch, with multiple basic
; blocks.
; RUN: %llvm2ice %s --verbose inst | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Simple smoke test of the call instruction. The assembly checks
; currently only verify the function labels.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -49,6 +52,7 @@ declare i32 @redirect_target(i32)
define void @call_void(i32 %n) {
; CHECK: define void @call_void
entry:
%cmp2 = icmp sgt i32 %n, 0
br i1 %cmp2, label %if.then, label %if.end
......
; RUIN: %llvm2ice --verbose none %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Test of multiple indirect calls to the same target. Each call
; should be to the same operand, whether it's in a register or on the
; stack.
; RUIN: %llvm2ice -O2 --verbose none %s | FileCheck %s
; RUN: %llvm2ice -Om1 --verbose none %s | FileCheck --check-prefix=OPTM1 %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -24,6 +29,12 @@ entry:
; CHECK: call [[REGISTER]]
; CHECK: call [[REGISTER]]
; CHECK: call [[REGISTER]]
;
; OPTM1: call [[TARGET:.+]]
; OPTM1: call [[TARGET]]
; OPTM1: call [[TARGET]]
; OPTM1: call [[TARGET]]
; OPTM1: call [[TARGET]]
; ERRORS-NOT: ICE translation error
; DUMP-NOT: SZ
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUIN: %llvm2ice -O2 --verbose none %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Simple test of non-fused compare/branch.
; RUIN: %llvm2ice -O2 --verbose none %s | FileCheck %s
; RUN: %llvm2ice -Om1 --verbose none %s | FileCheck --check-prefix=OPTM1 %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -27,8 +30,6 @@ if.end7: ; preds = %if.then5, %if.end
declare void @use(i1 zeroext)
; ERRORS-NOT: ICE translation error
; CHECK: .globl testBool
; Two bool computations
; CHECK: cmp
......@@ -40,4 +41,18 @@ declare void @use(i1 zeroext)
; CHECK: cmp
; CHECK: call
; CHECK: ret
;
; OPTM1: .globl testBool
; Two bool computations
; OPTM1: cmp
; OPTM1: cmp
; Test first bool
; OPTM1: cmp
; OPTM1: call
; Test second bool
; OPTM1: cmp
; OPTM1: call
; OPTM1: ret
; ERRORS-NOT: ICE translation error
; DUMP-NOT: SZ
; RUIN: %llvm2ice %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Simple test of signed and unsigned integer conversions.
; RUIN: %llvm2ice -O2 --verbose none %s | FileCheck %s
; RUN: %llvm2ice -Om1 --verbose none %s | FileCheck --check-prefix=OPTM1 %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -27,16 +30,27 @@ entry:
%__7 = bitcast [8 x i8]* @i64v to i64*
store i64 %v3, i64* %__7, align 1
ret void
; CHECK: mov al, byte ptr [
; CHECK-NEXT: movsx cx, al
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: movsx ecx, al
; CHECK-NEXT: mov dword ptr [
; CHECK-NEXT: movsx ecx, al
; CHECK-NEXT: sar eax, 31
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
}
; CHECK: from_int8:
; CHECK: mov al, byte ptr [
; CHECK-NEXT: movsx cx, al
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: movsx ecx, al
; CHECK-NEXT: mov dword ptr [
; CHECK-NEXT: movsx ecx, al
; CHECK-NEXT: sar eax, 31
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
;
; OPTM1: from_int8:
; OPTM1: mov {{.*}}, byte ptr [
; OPTM1: movsx
; OPTM1: mov word ptr [
; OPTM1: movsx
; OPTM1: mov dword ptr [
; OPTM1: movsx
; OPTM1: sar {{.*}}, 31
; OPTM1: i64v
define void @from_int16() {
entry:
......@@ -52,16 +66,26 @@ entry:
%__7 = bitcast [8 x i8]* @i64v to i64*
store i64 %v3, i64* %__7, align 1
ret void
; CHECK: mov ax, word ptr [
; CHECK-NEXT: mov cx, ax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: movsx ecx, ax
; CHECK-NEXT: mov dword ptr [
; CHECK-NEXT: movsx ecx, ax
; CHECK-NEXT: sar eax, 31
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
}
; CHECK: from_int16:
; CHECK: mov ax, word ptr [
; CHECK-NEXT: mov cx, ax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: movsx ecx, ax
; CHECK-NEXT: mov dword ptr [
; CHECK-NEXT: movsx ecx, ax
; CHECK-NEXT: sar eax, 31
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
;
; OPTM1: from_int16:
; OPTM1: mov {{.*}}, word ptr [
; OPTM1: i8v
; OPTM1: movsx
; OPTM1: i32v
; OPTM1: movsx
; OPTM1: sar {{.*}}, 31
; OPTM1: i64v
define void @from_int32() {
entry:
......@@ -77,16 +101,24 @@ entry:
%__7 = bitcast [8 x i8]* @i64v to i64*
store i64 %v3, i64* %__7, align 1
ret void
; CHECK: mov eax, dword ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: sar eax, 31
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
}
; CHECK: from_int32:
; CHECK: mov eax, dword ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: sar eax, 31
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
;
; OPTM1: from_int32:
; OPTM1: i32v
; OPTM1: i8v
; OPTM1: i16v
; OPTM1: sar {{.*}}, 31
; OPTM1: i64v
define void @from_int64() {
entry:
......@@ -102,13 +134,20 @@ entry:
%__7 = bitcast [4 x i8]* @i32v to i32*
store i32 %v3, i32* %__7, align 1
ret void
; CHECK: mov eax, dword ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: mov dword ptr [
}
; CHECK: from_int64:
; CHECK: mov eax, dword ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: mov dword ptr [
;
; OPTM1: from_int64:
; OPTM1: i64v
; OPTM1: i8v
; OPTM1: i16v
; OPTM1: i32v
define void @from_uint8() {
entry:
......@@ -124,16 +163,27 @@ entry:
%__7 = bitcast [8 x i8]* @i64v to i64*
store i64 %v3, i64* %__7, align 1
ret void
; CHECK: mov al, byte ptr [
; CHECK-NEXT: movzx cx, al
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: movzx ecx, al
; CHECK-NEXT: mov dword ptr [
; CHECK-NEXT: movzx eax, al
; CHECK-NEXT: mov ecx, 0
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
}
; CHECK: from_uint8:
; CHECK: mov al, byte ptr [
; CHECK-NEXT: movzx cx, al
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: movzx ecx, al
; CHECK-NEXT: mov dword ptr [
; CHECK-NEXT: movzx eax, al
; CHECK-NEXT: mov ecx, 0
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
;
; OPTM1: from_uint8:
; OPTM1: u8v
; OPTM1: movzx
; OPTM1: i16v
; OPTM1: movzx
; OPTM1: i32v
; OPTM1: movzx
; OPTM1: mov {{.*}}, 0
; OPTM1: i64v
define void @from_uint16() {
entry:
......@@ -149,16 +199,26 @@ entry:
%__7 = bitcast [8 x i8]* @i64v to i64*
store i64 %v3, i64* %__7, align 1
ret void
; CHECK: mov ax, word ptr [
; CHECK-NEXT: mov cx, ax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: movzx ecx, ax
; CHECK-NEXT: mov dword ptr [
; CHECK-NEXT: movzx eax, ax
; CHECK-NEXT: mov ecx, 0
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
}
; CHECK: from_uint16:
; CHECK: mov ax, word ptr [
; CHECK-NEXT: mov cx, ax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: movzx ecx, ax
; CHECK-NEXT: mov dword ptr [
; CHECK-NEXT: movzx eax, ax
; CHECK-NEXT: mov ecx, 0
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
;
; OPTM1: from_uint16:
; OPTM1: u16v
; OPTM1: i8v
; OPTM1: movzx
; OPTM1: i32v
; OPTM1: movzx
; OPTM1: mov {{.*}}, 0
; OPTM1: i64v
define void @from_uint32() {
entry:
......@@ -174,15 +234,23 @@ entry:
%__7 = bitcast [8 x i8]* @i64v to i64*
store i64 %v3, i64* %__7, align 1
ret void
; CHECK: mov eax, dword ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: mov ecx, 0
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
}
; CHECK: from_uint32:
; CHECK: mov eax, dword ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: mov ecx, 0
; CHECK-NEXT: mov dword ptr [i64v+4],
; CHECK-NEXT: mov dword ptr [i64v],
;
; OPTM1: from_uint32:
; OPTM1: u32v
; OPTM1: i8v
; OPTM1: i16v
; OPTM1: mov {{.*}}, 0
; OPTM1: i64v
define void @from_uint64() {
entry:
......@@ -198,13 +266,20 @@ entry:
%__7 = bitcast [4 x i8]* @i32v to i32*
store i32 %v3, i32* %__7, align 1
ret void
; CHECK: mov eax, dword ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: mov dword ptr [
}
; CHECK: from_uint64:
; CHECK: mov eax, dword ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov byte ptr [
; CHECK-NEXT: mov ecx, eax
; CHECK-NEXT: mov word ptr [
; CHECK-NEXT: mov dword ptr [
;
; OPTM1: from_uint64:
; OPTM1: u64v
; OPTM1: i8v
; OPTM1: i16v
; OPTM1: i32v
; ERRORS-NOT: ICE translation error
; DUMP-NOT: SZ
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Trivial test of a trivial function.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice --verbose none %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; This tries to be a comprehensive test of f32 and f64 operations.
; The CHECK lines are only checking for basic instruction patterns
; that should be present regardless of the optimization level, so
; there are no special OPTM1 match lines.
; RUIN: %llvm2ice -O2 --verbose none %s | FileCheck %s
; RUN: %llvm2ice -Om1 --verbose none %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
; This is a smoke test for floating-point constant pooling. It tests
; pooling of various float and double constants (including positive
; and negative NaN) within functions and across functions. Note that
......@@ -10,6 +6,11 @@
; number in a reasonable number of digits". See
; http://llvm.org/docs/LangRef.html#simple-constants .
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
@__init_array_start = internal constant [0 x i8] zeroinitializer, align 4
@__fini_array_start = internal constant [0 x i8] zeroinitializer, align 4
@__tls_template_start = internal constant [0 x i8] zeroinitializer, align 8
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Trivial test of the use of internal versus external global
; functions.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; Note: We don't run this test using a PNaCl bitcode file, because
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Trivial structural test of 64-bit icmp instructions.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Simple test of the load instruction.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -10,7 +12,8 @@ entry:
%iv = load i64* %__1, align 1
ret void
; CHECK: %__1 = i32 %addr_arg
; CHECK: Initial CFG
; CHECK: %__1 = i32 %addr_arg
; CHECK-NEXT: %iv = load i64* {{.*}}, align 1
; CHECK-NEXT: ret void
}
......@@ -21,6 +24,7 @@ entry:
%iv = load i32* %__1, align 1
ret void
; CHECK: Initial CFG
; CHECK: %__1 = i32 %addr_arg
; CHECK-NEXT: %iv = load i32* {{.*}}, align 1
; CHECK-NEXT: ret void
......@@ -32,6 +36,7 @@ entry:
%iv = load i16* %__1, align 1
ret void
; CHECK: Initial CFG
; CHECK: %__1 = i32 %addr_arg
; CHECK-NEXT: %iv = load i16* {{.*}}, align 1
; CHECK-NEXT: ret void
......@@ -43,6 +48,7 @@ entry:
%iv = load i8* %__1, align 1
ret void
; CHECK: Initial CFG
; CHECK: %__1 = i32 %addr_arg
; CHECK-NEXT: %iv = load i8* {{.*}}, align 1
; CHECK-NEXT: ret void
......
; Tests the Subzero "name mangling" when using the "llvm2ice --prefix"
; option.
; RUN: %llvm2ice --verbose none %s | FileCheck %s
; RUN: %llvm2ice --verbose none --prefix Subzero %s | FileCheck --check-prefix=MANGLE %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
define internal void @FuncC(i32 %i) {
entry:
ret void
}
; FuncC is a C symbol that isn't recognized as a C++ mangled symbol.
; CHECK: FuncC:
; MANGLE: SubzeroFuncC
define internal void @_ZN13TestNamespace4FuncEi(i32 %i) {
entry:
ret void
}
; This is Func(int) nested inside namespace TestNamespace.
; CHECK: _ZN13TestNamespace4FuncEi:
; MANGLE: _ZN7Subzero13TestNamespace4FuncEi:
define internal void @_ZN13TestNamespace15NestedNamespace4FuncEi(i32 %i) {
entry:
ret void
}
; This is Func(int) nested inside two namespaces.
; CHECK: _ZN13TestNamespace15NestedNamespace4FuncEi:
; MANGLE: _ZN7Subzero13TestNamespace15NestedNamespace4FuncEi:
define internal void @_Z13FuncCPlusPlusi(i32 %i) {
entry:
ret void
}
; This is a non-nested, mangled C++ symbol.
; CHECK: _Z13FuncCPlusPlusi:
; MANGLE: _ZN7Subzero13FuncCPlusPlusEi:
define internal void @_ZN12_GLOBAL__N_18FuncAnonEi(i32 %i) {
entry:
ret void
}
; This is FuncAnon(int) nested inside an anonymous namespace.
; CHECK: _ZN12_GLOBAL__N_18FuncAnonEi:
; MANGLE: _ZN7Subzero12_GLOBAL__N_18FuncAnonEi:
; Now for the illegitimate examples.
; Test for _ZN with no suffix. Don't crash, prepend Subzero.
define internal void @_ZN(i32 %i) {
entry:
ret void
}
; MANGLE: Subzero_ZN:
; Test for _Z<len><str> where <len> is smaller than it should be.
define internal void @_Z12FuncCPlusPlusi(i32 %i) {
entry:
ret void
}
; MANGLE: _ZN7Subzero12FuncCPlusPluEsi:
; Test for _Z<len><str> where <len> is slightly larger than it should be.
define internal void @_Z14FuncCPlusPlusi(i32 %i) {
entry:
ret void
}
; MANGLE: _ZN7Subzero14FuncCPlusPlusiE:
; Test for _Z<len><str> where <len> is much larger than it should be.
define internal void @_Z114FuncCPlusPlusi(i32 %i) {
entry:
ret void
}
; MANGLE: Subzero_Z114FuncCPlusPlusi:
; Test for _Z<len><str> where we try to overflow the uint32_t holding <len>.
define internal void @_Z4294967296FuncCPlusPlusi(i32 %i) {
entry:
ret void
}
; MANGLE: Subzero_Z4294967296FuncCPlusPlusi:
; Test for _Z<len><str> where <len> is 0.
define internal void @_Z0FuncCPlusPlusi(i32 %i) {
entry:
ret void
}
; MANGLE: _ZN7Subzero0EFuncCPlusPlusi:
; Test for _Z<len><str> where <len> is -1. LLVM explicitly allows the
; '-' character in identifiers.
define internal void @_Z-1FuncCPlusPlusi(i32 %i) {
entry:
ret void
}
; MANGLE: Subzero_Z-1FuncCPlusPlusi:
; ERRORS-NOT: ICE translation error
; DUMP-NOT: SZ
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Simple test of functions returning one of its arguments.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Simple test of the select instruction. The CHECK lines are only
; checking for basic instruction patterns that should be present
; regardless of the optimization level, so there are no special OPTM1
; match lines.
; RUIN: %llvm2ice -O2 --verbose none %s | FileCheck %s
; RUN: %llvm2ice -Om1 --verbose none %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; This is a test of C-level conversion operations that clang lowers
; into pairs of shifts.
; RUIN: %llvm2ice -O2 --verbose none %s | FileCheck %s
; RUN: %llvm2ice -Om1 --verbose none %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -17,9 +21,10 @@ entry:
%__4 = bitcast [4 x i8]* @i1 to i32*
store i32 %v1, i32* %__4, align 1
ret void
; CHECK: shl eax, 24
; CHECK-NEXT: sar eax, 24
}
; CHECK: conv1:
; CHECK: shl {{.*}}, 24
; CHECK: sar {{.*}}, 24
define void @conv2() {
entry:
......@@ -30,9 +35,10 @@ entry:
%__4 = bitcast [4 x i8]* @i2 to i32*
store i32 %v1, i32* %__4, align 1
ret void
; CHECK: shl eax, 16
; CHECK-NEXT: sar eax, 16
}
; CHECK: conv2:
; CHECK: shl {{.*}}, 16
; CHECK: sar {{.*}}, 16
; ERRORS-NOT: ICE translation error
; DUMP-NOT: SZ
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
define i64 @add_args_i64(i64 %arg1, i64 %arg2) {
entry:
%add = add i64 %arg2, %arg1
ret i64 %add
}
; Checks for verbose instruction output
; CHECK: define i64 @add_args
; CHECK: %add = add i64 %arg2, %arg1
; CHECK-NEXT: ret i64 %add
define i32 @add_args_i32(i32 %arg1, i32 %arg2) {
entry:
%add = add i32 %arg2, %arg1
ret i32 %add
}
; Checks for emitted assembly
; CHECK: .globl add_args_i32
; CHECK: mov eax, dword ptr [esp+4]
; CHECK-NEXT: mov ecx, dword ptr [esp+8]
; CHECK-NEXT: add ecx, eax
; CHECK-NEXT: mov eax, ecx
; CHECK-NEXT: ret
; ERRORS-NOT: ICE translation error
; DUMP-NOT: SZ
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
define internal i32 @simple_cond(i32 %a, i32 %n) {
entry:
%cmp = icmp slt i32 %n, 0
; CHECK: %cmp = icmp slt i32 %n, 0
br i1 %cmp, label %if.then, label %if.else
; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else
if.then:
%sub = sub i32 1, %n
br label %if.end
if.else:
%gep_array = mul i32 %n, 4
%gep = add i32 %a, %gep_array
%__6 = inttoptr i32 %gep to i32*
%v0 = load i32* %__6, align 1
br label %if.end
if.end:
%result.0 = phi i32 [ %sub, %if.then ], [ %v0, %if.else ]
; CHECK: %result.0 = phi i32 [ %sub, %if.then ], [ %v0, %if.else ]
ret i32 %result.0
}
; ERRORS-NOT: ICE translation error
; DUMP-NOT: SZ
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; This tests a simple loop that sums the elements of an input array.
; The O2 check patterns represent the best code currently achieved.
; RUIN: %llvm2ice -O2 --verbose none %s | FileCheck %s
; RUN: %llvm2ice -Om1 --verbose none %s | FileCheck --check-prefix=OPTM1 %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -26,17 +30,7 @@ for.end:
ret i32 %sum.0.lcssa
}
; Checks for verbose instruction output
; CHECK: br i1 %cmp4, label %for.body, label %for.end
; CHECK-NEXT: for.body
; CHECK: %i.06 = phi i32 [ %inc, %for.body ], [ 0, %entry ]
; CHECK-NEXT: %sum.05 = phi i32 [ %add, %for.body ], [ 0, %entry ]
; Checks for emitted assembly
; CHECK: .globl simple_loop
; CHECK: mov ecx, dword ptr [esp+{{[0-9]+}}]
; CHECK: cmp ecx, 0
; CHECK-NEXT: jg {{.*}}for.body
......@@ -49,6 +43,13 @@ for.end:
; CHECK-NEXT: mov [[ICMPREG:[a-z]+]], [[IREG]]
; CHECK: cmp [[ICMPREG]], ecx
; CHECK-NEXT: jl {{.*}}for.body
;
; There's nothing remarkable under Om1 to test for, since Om1 generates
; such atrocious code (by design).
; OPTM1: .globl simple_loop
; OPTM1: cmp {{.*}}, 0
; OPTM1: jg
; OPTM1: ret
; ERRORS-NOT: ICE translation error
; DUMP-NOT: SZ
; RUIN: %llvm2ice %s -verbose inst | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; Simple test of the store instruction.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -10,6 +12,7 @@ entry:
store i64 1, i64* %__1, align 1
ret void
; CHECK: Initial CFG
; CHECK: %__1 = i32 %addr_arg
; CHECK-NEXT: store i64 1, {{.*}}, align 1
; CHECK-NEXT: ret void
......@@ -21,6 +24,7 @@ entry:
store i32 1, i32* %__1, align 1
ret void
; CHECK: Initial CFG
; CHECK: %__1 = i32 %addr_arg
; CHECK-NEXT: store i32 1, {{.*}}, align 1
; CHECK-NEXT: ret void
......@@ -32,6 +36,7 @@ entry:
store i16 1, i16* %__1, align 1
ret void
; CHECK: Initial CFG
; CHECK: %__1 = i32 %addr_arg
; CHECK-NEXT: store i16 1, {{.*}}, align 1
; CHECK-NEXT: ret void
......@@ -43,6 +48,7 @@ entry:
store i8 1, i8* %__1, align 1
ret void
; CHECK: Initial CFG
; CHECK: %__1 = i32 %addr_arg
; CHECK-NEXT: store i8 1, {{.*}}, align 1
; CHECK-NEXT: ret void
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; This test is lowered from C code that does some simple aritmetic
; with struct members.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
; This file is lowered from C code that does some simple aritmetic with
; struct members. It's also built with the PNaCl toolchain so this is the
; stable ABI subset of LLVM IR (structs are gone, pointers turned into i32,
; geps gone, etc.)
define internal i32 @compute_important_function(i32 %v1, i32 %v2) {
entry:
%__2 = inttoptr i32 %v1 to i32*
......
; RUIN: %llvm2ice %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; This tests a switch statement, including multiple branches to the
; same label which also results in phi instructions with multiple
; entries for the same incoming edge.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......
; RUIN: %llvm2ice -verbose inst %s | FileCheck %s
; RUIN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; This tests the basic structure of the Unreachable instruction.
; RUN: %llvm2ice --verbose inst %s | FileCheck %s
; RUN: %llvm2ice --verbose none %s | FileCheck --check-prefix=ERRORS %s
; RUN: %llvm2iceinsts %s | %szdiff %s | FileCheck --check-prefix=DUMP %s
; RUN: %llvm2iceinsts --pnacl %s | %szdiff %s \
; RUN: | FileCheck --check-prefix=DUMP %s
......@@ -11,6 +13,7 @@ entry:
abort: ; preds = %entry
unreachable
; CHECK: unreachable
return: ; preds = %entry
%div = sdiv i32 %num, %den
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment