Skip to content

Commit 604b80c

Browse files
committed
C front-end: __builtin_shuffle, __builtin_shufflevector
These are polymorphic built-ins supported by GCC and Clang, respectively. Introduce a new shuffle_vector expression to handle these, with type checking addressing the differences between the GCC/Clang APIs.
1 parent 29a323f commit 604b80c

File tree

9 files changed

+352
-2
lines changed

9 files changed

+352
-2
lines changed

regression/cbmc/gcc_vector3/main.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include <assert.h>
2+
3+
#ifdef __GNUC__
4+
typedef int v4si __attribute__((vector_size(16)));
5+
6+
typedef union {
7+
v4si v;
8+
int members[4];
9+
} vector_u;
10+
#endif
11+
12+
int main()
13+
{
14+
#if defined(__GNUC__) && !defined(__clang__)
15+
// https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html
16+
v4si a = {1, 2, 3, 4};
17+
v4si b = {5, 6, 7, 8};
18+
v4si mask1 = {0, 1, 5, 3};
19+
v4si mask2 = {0, 4, 2, 5};
20+
21+
vector_u res;
22+
23+
res.v = __builtin_shuffle(a, mask1);
24+
assert(res.members[0] == 1);
25+
assert(res.members[1] == 2);
26+
assert(res.members[2] == 2);
27+
assert(res.members[3] == 4);
28+
29+
res.v = __builtin_shuffle(a, b, mask2);
30+
assert(res.members[0] == 1);
31+
assert(res.members[1] == 5);
32+
assert(res.members[2] == 3);
33+
assert(res.members[3] == 6);
34+
#elif defined(__clang__)
35+
v4si a = {1, 2, 3, 4};
36+
v4si b = {5, 6, 7, 8};
37+
38+
vector_u res;
39+
40+
res.v = __builtin_shufflevector(a, a, 0, 1, -1, 3);
41+
assert(res.members[0] == 1);
42+
assert(res.members[1] == 2);
43+
// res.members[2] is "don't care"
44+
assert(res.members[3] == 4);
45+
46+
res.v = __builtin_shufflevector(a, b, 0, 4, 2, 5);
47+
assert(res.members[0] == 1);
48+
assert(res.members[1] == 5);
49+
assert(res.members[2] == 3);
50+
assert(res.members[3] == 6);
51+
#endif
52+
}

regression/cbmc/gcc_vector3/test.desc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CORE broken-smt-backend
2+
main.c
3+
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^warning: ignoring

src/ansi-c/c_typecheck_base.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ class c_typecheck_baset:
208208
virtual code_blockt instantiate_gcc_polymorphic_builtin(
209209
const irep_idt &identifier,
210210
const symbol_exprt &function_symbol);
211+
virtual exprt
212+
typecheck_shuffle_vector(const side_effect_expr_function_callt &expr);
211213

212214
virtual void make_index_type(exprt &expr);
213215
virtual void make_constant(exprt &expr);

src/ansi-c/c_typecheck_expr.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,6 +1927,24 @@ void c_typecheck_baset::typecheck_side_effect_function_call(
19271927

19281928
return;
19291929
}
1930+
else if(
1931+
identifier == "__builtin_shuffle" &&
1932+
config.ansi_c.mode == configt::ansi_ct::flavourt::GCC)
1933+
{
1934+
exprt result = typecheck_shuffle_vector(expr);
1935+
expr.swap(result);
1936+
1937+
return;
1938+
}
1939+
else if(
1940+
identifier == "__builtin_shufflevector" &&
1941+
config.ansi_c.mode == configt::ansi_ct::flavourt::CLANG)
1942+
{
1943+
exprt result = typecheck_shuffle_vector(expr);
1944+
expr.swap(result);
1945+
1946+
return;
1947+
}
19301948
else if(
19311949
auto gcc_polymorphic = typecheck_gcc_polymorphic_builtin(
19321950
identifier, expr.arguments(), f_op.source_location()))

src/ansi-c/c_typecheck_gcc_polymorphic_builtins.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,3 +1377,128 @@ code_blockt c_typecheck_baset::instantiate_gcc_polymorphic_builtin(
13771377

13781378
return block;
13791379
}
1380+
1381+
exprt c_typecheck_baset::typecheck_shuffle_vector(
1382+
const side_effect_expr_function_callt &expr)
1383+
{
1384+
const exprt &f_op = expr.function();
1385+
const source_locationt &source_location = expr.source_location();
1386+
const irep_idt &identifier = to_symbol_expr(f_op).get_identifier();
1387+
1388+
exprt::operandst arguments = expr.arguments();
1389+
1390+
if(identifier == "__builtin_shuffle")
1391+
{
1392+
// https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html and
1393+
// https://github.com/OpenCL/man/blob/master/shuffle.adoc
1394+
if(arguments.size() != 2 && arguments.size() != 3)
1395+
{
1396+
error().source_location = f_op.source_location();
1397+
error() << "__builtin_shuffle expects two or three arguments" << eom;
1398+
throw 0;
1399+
}
1400+
1401+
for(exprt &arg : arguments)
1402+
{
1403+
if(arg.type().id() != ID_vector)
1404+
{
1405+
error().source_location = f_op.source_location();
1406+
error() << "__builtin_shuffle expects vector arguments" << eom;
1407+
throw 0;
1408+
}
1409+
}
1410+
1411+
const exprt &arg0 = arguments[0];
1412+
const vector_typet &input_vec_type = to_vector_type(arg0.type());
1413+
1414+
optionalt<exprt> arg1;
1415+
if(arguments.size() == 3)
1416+
{
1417+
if(arguments[1].type() != input_vec_type)
1418+
{
1419+
error().source_location = f_op.source_location();
1420+
error() << "__builtin_shuffle expects input vectors of the same type"
1421+
<< eom;
1422+
throw 0;
1423+
}
1424+
arg1 = arguments[1];
1425+
}
1426+
const exprt &indices = arguments.back();
1427+
const vector_typet &indices_type = to_vector_type(indices.type());
1428+
const std::size_t indices_size =
1429+
numeric_cast_v<std::size_t>(indices_type.size());
1430+
1431+
exprt::operandst operands;
1432+
operands.reserve(indices_size);
1433+
1434+
auto input_size = numeric_cast<mp_integer>(input_vec_type.size());
1435+
CHECK_RETURN(input_size.has_value());
1436+
if(arg1.has_value())
1437+
input_size = *input_size * 2;
1438+
constant_exprt size = from_integer(*input_size, indices_type.subtype());
1439+
1440+
for(std::size_t i = 0; i < indices_size; ++i)
1441+
{
1442+
// only the least significant bits of each mask element are considered
1443+
mod_exprt mod_index{index_exprt{indices, from_integer(i, index_type())},
1444+
size};
1445+
mod_index.add_source_location() = source_location;
1446+
operands.push_back(std::move(mod_index));
1447+
}
1448+
1449+
return shuffle_vector_exprt{arg0, arg1, std::move(operands)}.lower();
1450+
}
1451+
else if(identifier == "__builtin_shufflevector")
1452+
{
1453+
// https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector
1454+
if(arguments.size() < 2)
1455+
{
1456+
error().source_location = f_op.source_location();
1457+
error() << "__builtin_shufflevector expects two or more arguments" << eom;
1458+
throw 0;
1459+
}
1460+
1461+
exprt::operandst operands;
1462+
operands.reserve(arguments.size() - 2);
1463+
1464+
for(std::size_t i = 0; i < arguments.size(); ++i)
1465+
{
1466+
exprt &arg_i = arguments[i];
1467+
1468+
if(i <= 1 && arg_i.type().id() != ID_vector)
1469+
{
1470+
error().source_location = f_op.source_location();
1471+
error() << "__builtin_shufflevector expects two vectors as argument"
1472+
<< eom;
1473+
throw 0;
1474+
}
1475+
else if(i > 1)
1476+
{
1477+
if(!is_signed_or_unsigned_bitvector(arg_i.type()))
1478+
{
1479+
error().source_location = f_op.source_location();
1480+
error() << "__builtin_shufflevector expects integer index" << eom;
1481+
throw 0;
1482+
}
1483+
1484+
make_constant(arg_i);
1485+
1486+
const auto int_index = numeric_cast<mp_integer>(arg_i);
1487+
CHECK_RETURN(int_index.has_value());
1488+
1489+
if(*int_index == -1)
1490+
{
1491+
operands.push_back(from_integer(0, arg_i.type()));
1492+
operands.back().add_source_location() = source_location;
1493+
}
1494+
else
1495+
operands.push_back(arg_i);
1496+
}
1497+
}
1498+
1499+
return shuffle_vector_exprt{arguments[0], arguments[1], std::move(operands)}
1500+
.lower();
1501+
}
1502+
else
1503+
UNREACHABLE;
1504+
}

src/ansi-c/clang_builtin_headers.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
__gcc_v4sf __builtin_shufflevector(__gcc_v4sf, __gcc_v4sf, ...);
2-
31
__gcc_v2di __builtin_ia32_undef128(void);
42
__gcc_v4di __builtin_ia32_undef256(void);
53
__gcc_v8di __builtin_ia32_undef512(void);

src/util/irep_ids.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,7 @@ IREP_ID_TWO(vector_ge, vector->=)
849849
IREP_ID_TWO(vector_le, vector-<=)
850850
IREP_ID_TWO(vector_gt, vector->)
851851
IREP_ID_TWO(vector_lt, vector-<)
852+
IREP_ID_ONE(shuffle_vector)
852853

853854
// Projects depending on this code base that wish to extend the list of
854855
// available ids should provide a file local_irep_ids.def in their source tree

src/util/std_expr.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,56 @@ void let_exprt::validate(const exprt &expr, const validation_modet vm)
143143
"let bindings must be type consistent");
144144
}
145145
}
146+
147+
shuffle_vector_exprt::shuffle_vector_exprt(
148+
exprt vector1,
149+
optionalt<exprt> vector2,
150+
exprt::operandst indices)
151+
: multi_ary_exprt(
152+
ID_shuffle_vector,
153+
{std::move(vector1), nil_exprt{}, exprt{}},
154+
typet{})
155+
{
156+
if(vector2.has_value())
157+
op1() = std::move(*vector2);
158+
159+
const vector_typet &vt = to_vector_type(op0().type());
160+
type() =
161+
vector_typet{vt.subtype(), from_integer(indices.size(), vt.size().type())};
162+
163+
op2().operands().swap(indices);
164+
}
165+
166+
vector_exprt shuffle_vector_exprt::lower() const
167+
{
168+
PRECONDITION(
169+
!has_two_input_vectors() || vector1().type() == vector2().type());
170+
171+
if(indices().empty())
172+
return vector_exprt{exprt::operandst{}, type()};
173+
174+
auto input_size =
175+
numeric_cast<mp_integer>(to_vector_type(vector1().type()).size());
176+
CHECK_RETURN(input_size.has_value());
177+
178+
exprt::operandst operands;
179+
operands.reserve(indices().size());
180+
181+
for(const exprt &index : indices())
182+
{
183+
if(has_two_input_vectors())
184+
{
185+
operands.push_back(if_exprt{
186+
binary_predicate_exprt{
187+
index, ID_lt, from_integer(*input_size, index.type())},
188+
index_exprt{vector1(), index},
189+
index_exprt{
190+
vector2(),
191+
minus_exprt{index, from_integer(*input_size, index.type())}}});
192+
}
193+
else
194+
operands.push_back(index_exprt{vector1(), index});
195+
}
196+
197+
return vector_exprt{std::move(operands), type()};
198+
}

src/util/std_expr.h

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3202,4 +3202,97 @@ inline bool can_cast_expr<class_method_descriptor_exprt>(const exprt &base)
32023202
return base.id() == ID_virtual_function;
32033203
}
32043204

3205+
/// \brief Shuffle elements of one or two vectors, modelled after Clang's
3206+
/// __builtin_shufflevector.
3207+
class shuffle_vector_exprt : public multi_ary_exprt
3208+
{
3209+
public:
3210+
shuffle_vector_exprt(
3211+
exprt vector1,
3212+
optionalt<exprt> vector2,
3213+
exprt::operandst indices);
3214+
3215+
const vector_typet &type() const
3216+
{
3217+
return static_cast<const vector_typet &>(multi_ary_exprt::type());
3218+
}
3219+
3220+
vector_typet &type()
3221+
{
3222+
return static_cast<vector_typet &>(multi_ary_exprt::type());
3223+
}
3224+
3225+
const exprt &vector1() const
3226+
{
3227+
return op0();
3228+
}
3229+
3230+
exprt &vector1()
3231+
{
3232+
return op0();
3233+
}
3234+
3235+
const exprt &vector2() const
3236+
{
3237+
return op1();
3238+
}
3239+
3240+
exprt &vector2()
3241+
{
3242+
return op1();
3243+
}
3244+
3245+
bool has_two_input_vectors() const
3246+
{
3247+
return vector2().is_not_nil();
3248+
}
3249+
3250+
const exprt::operandst &indices() const
3251+
{
3252+
return op2().operands();
3253+
}
3254+
3255+
exprt::operandst &indices()
3256+
{
3257+
return op2().operands();
3258+
}
3259+
3260+
vector_exprt lower() const;
3261+
};
3262+
3263+
template <>
3264+
inline bool can_cast_expr<shuffle_vector_exprt>(const exprt &base)
3265+
{
3266+
return base.id() == ID_shuffle_vector;
3267+
}
3268+
3269+
inline void validate_expr(const shuffle_vector_exprt &value)
3270+
{
3271+
validate_operands(value, 3, "shuffle_vector must have three operands");
3272+
}
3273+
3274+
/// \brief Cast an exprt to a \ref shuffle_vector_exprt
3275+
///
3276+
/// \a expr must be known to be \ref shuffle_vector_exprt.
3277+
///
3278+
/// \param expr: Source expression
3279+
/// \return Object of type \ref shuffle_vector_exprt
3280+
inline const shuffle_vector_exprt &to_shuffle_vector_expr(const exprt &expr)
3281+
{
3282+
PRECONDITION(expr.id() == ID_shuffle_vector);
3283+
const shuffle_vector_exprt &ret =
3284+
static_cast<const shuffle_vector_exprt &>(expr);
3285+
validate_expr(ret);
3286+
return ret;
3287+
}
3288+
3289+
/// \copydoc to_shuffle_vector_expr(const exprt &)
3290+
inline shuffle_vector_exprt &to_shuffle_vector_expr(exprt &expr)
3291+
{
3292+
PRECONDITION(expr.id() == ID_shuffle_vector);
3293+
shuffle_vector_exprt &ret = static_cast<shuffle_vector_exprt &>(expr);
3294+
validate_expr(ret);
3295+
return ret;
3296+
}
3297+
32053298
#endif // CPROVER_UTIL_STD_EXPR_H

0 commit comments

Comments
 (0)