Skip to content

Commit 8261810

Browse files
committed
Encode empty structs as as zero byte
1 parent 496fe33 commit 8261810

File tree

3 files changed

+89
-5
lines changed

3 files changed

+89
-5
lines changed

src/solvers/smt2_incremental/encoding/struct_encoding.cpp

+16-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "struct_encoding.h"
44

5+
#include <util/arith_tools.h>
56
#include <util/bitvector_expr.h>
67
#include <util/bitvector_types.h>
78
#include <util/make_unique.h>
@@ -38,9 +39,14 @@ typet struct_encodingt::encode(typet type) const
3839
work_queue.pop();
3940
if(const auto struct_tag = type_try_dynamic_cast<struct_tag_typet>(current))
4041
{
41-
const auto width = (*boolbv_width)(*struct_tag);
42+
auto width = (*boolbv_width)(*struct_tag);
43+
// The bit vector theory of SMT disallows zero bit length length bit
44+
// vectors. C++ gives a minimum size for a struct (even an empty struct)
45+
// as being one byte; in order to ensure that structs have unique memory
46+
// locations. Therefore encoding empty structs as having 8 bits / 1 byte
47+
// is a reasonable solution in this case.
4248
if(width == 0)
43-
UNIMPLEMENTED_FEATURE("support for zero bit width structs.");
49+
width = 8;
4450
current = bv_typet{width};
4551
}
4652
if(const auto array = type_try_dynamic_cast<array_typet>(current))
@@ -102,11 +108,16 @@ static exprt encode(const with_exprt &with, const namespacet &ns)
102108
return struct_exprt{components, tag_type};
103109
}
104110

111+
/// Non-empty structs are flattened into a large bit vector using concatenation
112+
/// to express all the member operands of \p struct_expr. Empty structs are
113+
/// encoded as a zero byte. This has useful properties such as -
114+
/// * A zero byte is valid SMT, unlike zero length bit vectors.
115+
/// * Any two zero byte instances are always equal. This property would not
116+
/// be true of two instances of a non-det byte for instance.
105117
static exprt encode(const struct_exprt &struct_expr)
106118
{
107-
INVARIANT(
108-
!struct_expr.operands().empty(),
109-
"empty structs may not be encoded into SMT terms.");
119+
if(struct_expr.operands().empty())
120+
return from_integer(0, bv_typet{8});
110121
if(struct_expr.operands().size() == 1)
111122
return struct_expr.operands().front();
112123
return concatenation_exprt{struct_expr.operands(), struct_expr.type()};

unit/solvers/smt2_incremental/encoding/struct_encoding.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,34 @@ TEST_CASE("decoding into struct expressions.", "[core][smt2_incremental]")
341341
REQUIRE(test.struct_encoding.decode(encoded, struct_tag) == expected);
342342
}
343343

344+
TEST_CASE("encoding of struct with no members", "[core][smt2_incremental]")
345+
{
346+
auto test = struct_encoding_test_environmentt::make();
347+
const struct_union_typet::componentst component_types{};
348+
const type_symbolt type_symbol{"emptyt", struct_typet{component_types}, ID_C};
349+
test.symbol_table.insert(type_symbol);
350+
const struct_tag_typet type_reference{type_symbol.name};
351+
const auto expected_type = bv_typet{8};
352+
const auto expected_zero_byte = from_integer(0, expected_type);
353+
SECTION("Type of empty struct")
354+
{
355+
REQUIRE(test.struct_encoding.encode(type_reference) == expected_type);
356+
}
357+
SECTION("Empty struct typed symbol expression")
358+
{
359+
const symbolt empty_value_symbol{"foo", type_reference, ID_C};
360+
test.symbol_table.insert(empty_value_symbol);
361+
const auto symbol_expr = empty_value_symbol.symbol_expr();
362+
const exprt expected_symbol = symbol_exprt{"foo", expected_type};
363+
REQUIRE(test.struct_encoding.encode(symbol_expr) == expected_symbol);
364+
}
365+
SECTION("Struct expression")
366+
{
367+
const struct_exprt empty{{}, type_reference};
368+
REQUIRE(test.struct_encoding.encode(empty) == expected_zero_byte);
369+
}
370+
}
371+
344372
TEST_CASE(
345373
"encoding of single member struct expressions",
346374
"[core][smt2_incremental]")

unit/solvers/smt2_incremental/smt2_incremental_decision_procedure.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -1000,3 +1000,48 @@ TEST_CASE(
10001000

10011001
REQUIRE(returned == constant_exprt{"true", bool_typet{}});
10021002
}
1003+
1004+
TEST_CASE(
1005+
"smt2_incremental_decision_proceduret getting value of empty struct",
1006+
"[core][smt2_incremental]")
1007+
{
1008+
auto test = decision_procedure_test_environmentt::make();
1009+
const struct_union_typet::componentst component_types{};
1010+
const type_symbolt type_symbol{"emptyt", struct_typet{component_types}, ID_C};
1011+
test.symbol_table.insert(type_symbol);
1012+
const struct_tag_typet type_reference{type_symbol.name};
1013+
const symbolt foo{"foo", type_reference, ID_C};
1014+
test.symbol_table.insert(foo);
1015+
const symbolt bar{"bar", type_reference, ID_C};
1016+
test.symbol_table.insert(bar);
1017+
1018+
INFO("Sanity checking decision procedure and flushing size definitions");
1019+
test.mock_responses.push_front(smt_check_sat_responset{smt_sat_responset{}});
1020+
CHECK(test.procedure() == decision_proceduret::resultt::D_SATISFIABLE);
1021+
test.sent_commands.clear();
1022+
1023+
INFO("Defining an equality over empty structs");
1024+
const auto equality_expression =
1025+
test.procedure.handle(equal_exprt{foo.symbol_expr(), bar.symbol_expr()});
1026+
test.procedure.set_to(equality_expression, true);
1027+
const smt_identifier_termt expected_foo{"foo", smt_bit_vector_sortt{8}};
1028+
const smt_identifier_termt expected_bar{"bar", smt_bit_vector_sortt{8}};
1029+
const smt_identifier_termt expected_handle{"B0", smt_bool_sortt{}};
1030+
const smt_termt expected_equality =
1031+
smt_core_theoryt::equal(expected_foo, expected_bar);
1032+
const std::vector<smt_commandt> expected_problem_commands{
1033+
smt_declare_function_commandt{expected_foo, {}},
1034+
smt_declare_function_commandt{expected_bar, {}},
1035+
smt_define_function_commandt{"B0", {}, expected_equality},
1036+
smt_assert_commandt{expected_equality}};
1037+
REQUIRE(test.sent_commands == expected_problem_commands);
1038+
test.sent_commands.clear();
1039+
1040+
INFO("Ensuring decision procedure is in suitable state for getting output.");
1041+
const std::vector<smt_commandt> expected_check_commands{
1042+
smt_check_sat_commandt{}};
1043+
test.mock_responses.push_front(smt_check_sat_responset{smt_sat_responset{}});
1044+
REQUIRE(test.procedure() == decision_proceduret::resultt::D_SATISFIABLE);
1045+
CHECK(test.sent_commands == expected_check_commands);
1046+
test.sent_commands.clear();
1047+
}

0 commit comments

Comments
 (0)