Skip to content

Commit b68457c

Browse files
authored
Merge pull request #5127 from smowton/smowton/feature/lift-clinit-calls
Optionally lift clinit calls after Java method conversion
2 parents fde3f24 + 8701764 commit b68457c

21 files changed

+281
-10
lines changed
319 Bytes
Binary file not shown.
462 Bytes
Binary file not shown.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
public class Test {
2+
3+
public static void main(boolean unknown) {
4+
5+
int total = 0;
6+
7+
for(int i = 0; i < 10; ++i) {
8+
if(unknown)
9+
total += Other.x;
10+
}
11+
12+
}
13+
14+
}
15+
16+
class Other {
17+
18+
static int x = 1;
19+
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
Test.class
3+
--function Test.main --java-lift-clinit-calls
4+
activate-multi-line-match
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
Unwinding recursion java::Other\.<clinit_wrapper> iteration 1\nUnwinding loop java::Test\.main:\(Z\)V\.0 iteration 1
8+
--
9+
Unwinding recursion java::Other\.<clinit_wrapper> iteration 1\nUnwinding loop java::Test\.main:\(Z\)V\.0 iteration 2
10+
--
11+
This checks that the clinit-wrapper function is called prior to iteration 1 of the loop, but not iteration 2.
12+
319 Bytes
Binary file not shown.
511 Bytes
Binary file not shown.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
public class Test {
2+
3+
public static void main(boolean unknown, boolean other_unknown) {
4+
5+
int total = 0;
6+
7+
for(int i = 0; i < 10; ++i) {
8+
if(unknown)
9+
total += Other.x;
10+
else if(other_unknown)
11+
total -= Other.x;
12+
}
13+
14+
}
15+
16+
}
17+
18+
class Other {
19+
20+
static int x = 1;
21+
22+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE symex-driven-lazy-loading-expected-failure
2+
Test.class
3+
--function Test.main --java-lift-clinit-calls --show-goto-functions
4+
activate-multi-line-match
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
\s*// 0 no location\n\s*Other\.<clinit_wrapper>\(\);\s*// 1 no location\s*int total;
8+
--
9+
--
10+
This checks that the clinit-wrapper function is called once, even though there are two calls in the source
11+
With symex-driven loading there's an exception check after the clinit_wrapper function finishes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
Test.class
3+
--function Test.main --java-lift-clinit-calls
4+
activate-multi-line-match
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
Unwinding recursion java::Other\.<clinit_wrapper> iteration 1\nUnwinding loop java::Test\.main:\(ZZ\)V\.0 iteration 1
8+
--
9+
Unwinding recursion java::Other\.<clinit_wrapper> iteration 1\nUnwinding loop java::Test\.main:\(ZZ\)V\.0 iteration 2
10+
--
11+
This checks that the clinit-wrapper function is called prior to iteration 1 of the loop, but not iteration 2.
12+
319 Bytes
Binary file not shown.
527 Bytes
Binary file not shown.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
public class Test {
2+
3+
public static void main(boolean unknown, boolean other_unknown) {
4+
5+
int total = 0;
6+
7+
for(int i = 0; i < 10; ++i) {
8+
if(unknown)
9+
total += Other.x;
10+
else if(other_unknown)
11+
total -= Third.x;
12+
}
13+
14+
}
15+
16+
}
17+
18+
class Other {
19+
20+
static int x = 1;
21+
22+
}
23+
24+
class Third {
25+
26+
static int x = 1;
27+
28+
}
319 Bytes
Binary file not shown.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE symex-driven-lazy-loading-expected-failure
2+
Test.class
3+
--function Test.main --java-lift-clinit-calls --show-goto-functions
4+
activate-multi-line-match
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
\s*// 0 no location\n\s*Other\.<clinit_wrapper>\(\);\s*// 1 no location\s*Third\.<clinit_wrapper>\(\);\s*// 2 no location\s*int total;
8+
--
9+
--
10+
This checks that the clinit-wrapper function is called once, even though there are two calls in the source
11+
With symex-driven loading there's an exception check after the clinit_wrapper function
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
Test.class
3+
--function Test.main --java-lift-clinit-calls
4+
activate-multi-line-match
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
Unwinding recursion java::Other\.<clinit_wrapper> iteration 1\nUnwinding recursion java::Third\.<clinit_wrapper> iteration 1\nUnwinding loop java::Test\.main:\(ZZ\)V\.0 iteration 1
8+
--
9+
Unwinding recursion java::Other\.<clinit_wrapper> iteration 1\nUnwinding recursion java::Third\.<clinit_wrapper> iteration 1\nUnwinding loop java::Test\.main:\(ZZ\)V\.0 iteration 2
10+
--
11+
This checks that the clinit-wrapper functions are called prior to iteration 1 of the loop, but not iteration 2.
12+

jbmc/src/java_bytecode/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ SRC = assignments_from_json.cpp \
4343
java_trace_validation.cpp \
4444
java_types.cpp \
4545
java_utils.cpp \
46+
lift_clinit_calls.cpp \
4647
lambda_synthesis.cpp \
4748
load_method_by_regex.cpp \
4849
mz_zip_archive.cpp \

jbmc/src/java_bytecode/java_bytecode_language.cpp

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Author: Daniel Kroening, [email protected]
4242
#include "java_string_literals.h"
4343
#include "java_utils.h"
4444
#include "lambda_synthesis.h"
45+
#include "lift_clinit_calls.h"
4546

4647
#include "expr2java.h"
4748
#include "load_method_by_regex.h"
@@ -104,6 +105,8 @@ void parse_java_language_options(const cmdlinet &cmd, optionst &options)
104105
{
105106
options.set_option("static-values", cmd.get_value("static-values"));
106107
}
108+
options.set_option(
109+
"java-lift-clinit-calls", cmd.isset("java-lift-clinit-calls"));
107110
}
108111

109112
prefix_filtert get_context(const optionst &options)
@@ -247,6 +250,8 @@ java_bytecode_language_optionst::java_bytecode_language_optionst(
247250

248251
if(options.is_set("context-include") || options.is_set("context-exclude"))
249252
method_context = get_context(options);
253+
254+
should_lift_clinit_calls = options.get_bool_option("java-lift-clinit-calls");
250255
}
251256

252257
/// Consume options that are java bytecode specific.
@@ -1174,7 +1179,8 @@ static void notify_static_method_calls(
11741179
}
11751180

11761181
/// \brief Convert a method (one whose type is known but whose body hasn't
1177-
/// been converted) but don't run typecheck, etc
1182+
/// been converted) if it doesn't already have a body, and lift clinit calls
1183+
/// if requested by the user.
11781184
/// \remarks Amends the symbol table entry for function `function_id`, which
11791185
/// should be a method provided by this instance of `java_bytecode_languaget`
11801186
/// to have a value representing the method body.
@@ -1197,6 +1203,41 @@ bool java_bytecode_languaget::convert_single_method(
11971203
return false;
11981204
INVARIANT(declaring_class(symbol), "Method must have a declaring class.");
11991205

1206+
bool ret = convert_single_method_code(
1207+
function_id, symbol_table, needed_lazy_methods, class_to_declared_symbols);
1208+
1209+
if(symbol.value.is_not_nil() && language_options->should_lift_clinit_calls)
1210+
{
1211+
auto &writable_symbol = symbol_table.get_writeable_ref(function_id);
1212+
writable_symbol.value =
1213+
lift_clinit_calls(std::move(to_code(writable_symbol.value)));
1214+
}
1215+
1216+
INVARIANT(declaring_class(symbol), "Method must have a declaring class.");
1217+
1218+
return ret;
1219+
}
1220+
1221+
/// \brief Convert a method (one whose type is known but whose body hasn't
1222+
/// been converted) but don't run typecheck, etc
1223+
/// \remarks Amends the symbol table entry for function `function_id`, which
1224+
/// should be a method provided by this instance of `java_bytecode_languaget`
1225+
/// to have a value representing the method body.
1226+
/// \param function_id: method ID to convert
1227+
/// \param symbol_table: global symbol table
1228+
/// \param needed_lazy_methods: optionally a collection of needed methods to
1229+
/// update with any methods touched during the conversion
1230+
/// \param class_to_declared_symbols: maps classes to the symbols that
1231+
/// they declare.
1232+
bool java_bytecode_languaget::convert_single_method_code(
1233+
const irep_idt &function_id,
1234+
symbol_table_baset &symbol_table,
1235+
optionalt<ci_lazy_methods_neededt> needed_lazy_methods,
1236+
lazy_class_to_declared_symbols_mapt &class_to_declared_symbols)
1237+
{
1238+
const auto &symbol = symbol_table.lookup_ref(function_id);
1239+
PRECONDITION(symbol.value.is_nil());
1240+
12001241
// Get bytecode for specified function if we have it
12011242
method_bytecodet::opt_reft cmb = method_bytecode.get(function_id);
12021243

@@ -1219,8 +1260,6 @@ bool java_bytecode_languaget::convert_single_method(
12191260
// Add these to the needed_lazy_methods collection
12201261
notify_static_method_calls(generated_code, needed_lazy_methods);
12211262
writable_symbol.value = std::move(generated_code);
1222-
INVARIANT(
1223-
declaring_class(writable_symbol), "Method must have a declaring class.");
12241263
return false;
12251264
}
12261265
else if(
@@ -1305,8 +1344,6 @@ bool java_bytecode_languaget::convert_single_method(
13051344
// function:
13061345
notify_static_method_calls(
13071346
to_code(writable_symbol.value), needed_lazy_methods);
1308-
INVARIANT(
1309-
declaring_class(writable_symbol), "Method must have a declaring class.");
13101347
return false;
13111348
}
13121349

@@ -1327,7 +1364,6 @@ bool java_bytecode_languaget::convert_single_method(
13271364
language_options->threading_support,
13281365
language_options->method_context,
13291366
language_options->assert_no_exceptions_thrown);
1330-
INVARIANT(declaring_class(symbol), "Method must have a declaring class.");
13311367
return false;
13321368
}
13331369

@@ -1354,7 +1390,6 @@ bool java_bytecode_languaget::convert_single_method(
13541390
}
13551391
}
13561392

1357-
INVARIANT(declaring_class(symbol), "Method must have a declaring class.");
13581393
return true;
13591394
}
13601395

jbmc/src/java_bytecode/java_bytecode_language.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ Author: Daniel Kroening, [email protected]
4949
"(lazy-methods-extra-entry-point):" \
5050
"(java-load-class):" \
5151
"(java-no-load-class):" \
52-
"(static-values):"
52+
"(static-values):" \
53+
"(java-lift-clinit-calls)"
5354

5455
#define JAVA_BYTECODE_LANGUAGE_OPTIONS_HELP /*NOLINT*/ \
5556
" --disable-uncaught-exception-check\n" \
@@ -117,7 +118,12 @@ Author: Daniel Kroening, [email protected]
117118
" instead of calling the normal static initializer\n"/* NOLINT(*) */ \
118119
" (clinit) method.\n" \
119120
" The argument can be a relative or absolute path to\n"/* NOLINT(*) */ \
120-
" the file.\n"
121+
" the file.\n" \
122+
" --java-lift-clinit-calls Lifts clinit calls in function bodies to the top of the\n" /* NOLINT(*) */ \
123+
" function. This may reduce the overall cost of static\n" /* NOLINT(*) */ \
124+
" initialisation, but may be unsound if there are\n" /* NOLINT(*) */ \
125+
" cyclic dependencies between static initializers due\n" /* NOLINT(*) */ \
126+
" to potentially changing their order of execution." /* NOLINT(*) */
121127
// clang-format on
122128

123129
class symbolt;
@@ -200,6 +206,11 @@ struct java_bytecode_language_optionst
200206
/// same kind of "return nondet null or instance of return type" body that we
201207
/// use for stubbed methods. The original method body will never be loaded.
202208
optionalt<prefix_filtert> method_context;
209+
210+
/// Should we lift clinit calls in function bodies to the top? For example,
211+
/// turning `if(x) A.clinit() else B.clinit()` into
212+
/// `A.clinit(); B.clinit(); if(x) ...`
213+
bool should_lift_clinit_calls;
203214
};
204215

205216
#define JAVA_CLASS_MODEL_SUFFIX "@class_model"
@@ -290,6 +301,11 @@ class java_bytecode_languaget:public languaget
290301
symbol_table_baset &symbol_table,
291302
optionalt<ci_lazy_methods_neededt> needed_lazy_methods,
292303
lazy_class_to_declared_symbols_mapt &class_to_declared_symbols);
304+
bool convert_single_method_code(
305+
const irep_idt &function_id,
306+
symbol_table_baset &symbol_table,
307+
optionalt<ci_lazy_methods_neededt> needed_lazy_methods,
308+
lazy_class_to_declared_symbols_mapt &class_to_declared_symbols);
293309

294310
bool do_ci_lazy_method_conversion(symbol_tablet &);
295311
const select_pointer_typet &get_pointer_type_selector() const;
@@ -302,7 +318,6 @@ class java_bytecode_languaget:public languaget
302318
method_bytecodet method_bytecode;
303319
java_string_library_preprocesst string_preprocess;
304320

305-
306321
private:
307322
virtual std::vector<load_extra_methodst>
308323
build_extra_entry_points(const optionst &) const;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*******************************************************************\
2+
3+
Module: Static initializer call lifting
4+
5+
Author: Diffblue Ltd.
6+
7+
\*******************************************************************/
8+
9+
/// file Static initializer call lifting
10+
11+
#include "lift_clinit_calls.h"
12+
13+
#include <java_bytecode/java_static_initializers.h>
14+
15+
#include <util/expr_iterator.h>
16+
17+
#include <algorithm>
18+
19+
codet lift_clinit_calls(codet input)
20+
{
21+
// 1. Gather any clinit calls present in `input`:
22+
std::vector<symbol_exprt> clinit_wrappers_called;
23+
24+
for(auto it = input.depth_begin(), itend = input.depth_end(); it != itend;
25+
++it)
26+
{
27+
if(const auto code = expr_try_dynamic_cast<codet>(*it))
28+
{
29+
if(code->get_statement() == ID_function_call)
30+
{
31+
if(
32+
const auto callee = expr_try_dynamic_cast<symbol_exprt>(
33+
to_code_function_call(*code).function()))
34+
{
35+
if(is_clinit_wrapper_function(callee->get_identifier()))
36+
{
37+
clinit_wrappers_called.push_back(*callee);
38+
// Replace call with skip:
39+
it.mutate() = code_skipt();
40+
}
41+
}
42+
}
43+
}
44+
}
45+
46+
if(clinit_wrappers_called.empty())
47+
return input;
48+
49+
// 2. Unique, such that each clinit method is only called once:
50+
std::sort(clinit_wrappers_called.begin(), clinit_wrappers_called.end());
51+
auto delete_after =
52+
std::unique(clinit_wrappers_called.begin(), clinit_wrappers_called.end());
53+
clinit_wrappers_called.erase(delete_after, clinit_wrappers_called.end());
54+
55+
// 3. Lift static init calls to top of function:
56+
code_blockt result;
57+
for(const auto &callee : clinit_wrappers_called)
58+
{
59+
code_function_callt new_call(callee);
60+
// Nuke the source location, now that the call doesn't really relate to any
61+
// one particular line:
62+
new_call.function().drop_source_location();
63+
result.add(new_call);
64+
}
65+
66+
result.add(std::move(input));
67+
68+
return std::move(result);
69+
}

0 commit comments

Comments
 (0)