Skip to content

Commit abebaa6

Browse files
jmcmillan1zvonimirJoshua McMillan
authored
Implemented portfolio solving
Added portfolio command line option. Implemented multi-threaded portfolio solving. Co-authored-by: Zvonimir <zvonimir@cs.utah.edu> Co-authored-by: Joshua McMillan <jem@kornat.cs.utah.edu>
1 parent 28932f3 commit abebaa6

File tree

5 files changed

+233
-73
lines changed

5 files changed

+233
-73
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/share/smack
219219
USE_SOURCE_PERMISSIONS
220220
FILES_MATCHING PATTERN "*.py" PATTERN "*.h" PATTERN "*.c" PATTERN "Makefile"
221221
PATTERN "*.rs" PATTERN "*.f90" PATTERN "*.di" PATTERN "*.toml"
222-
PATTERN "*.cpp" PATTERN "cassert"
222+
PATTERN "*.cpp" PATTERN "cassert" PATTERN "*.yaml"
223223
)
224224

225225
install(FILES

share/smack/default-portfolio.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
thread1:
3+
verifier: "corral"
4+
5+
thread2:
6+
verifier: "corral"
7+
verifier-options: "/bopt:proverOpt:O:smt.qi.eager_threshold=100"
8+
9+
thread3:
10+
verifier: "corral"
11+
verifier-options: "/bopt:proverOpt:O:smt.arith.solver=2"
12+
13+
thread4:
14+
verifier: "corral"
15+
verifier-options: "/bopt:proverOpt:O:smt.arith.solver=2
16+
/bopt:proverOpt:O:smt.qi.eager_threshold=100"
17+

share/smack/frontend.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import re
44
import json
55
from .utils import temporary_file, try_command, temporary_directory,\
6-
llvm_exact_bin
6+
llvm_exact_bin, smack_root, smack_header_path, smack_headers,\
7+
smack_lib
78
from .versions import RUST_VERSION
89

910
# Needed for cargo operations
@@ -68,24 +69,6 @@ def extra_libs():
6869
}
6970

7071

71-
def smack_root():
72-
return os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
73-
74-
75-
def smack_header_path():
76-
return os.path.join(smack_root(), 'share', 'smack', 'include')
77-
78-
79-
def smack_headers(args):
80-
paths = []
81-
paths.append(smack_header_path())
82-
return paths
83-
84-
85-
def smack_lib():
86-
return os.path.join(smack_root(), 'share', 'smack', 'lib')
87-
88-
8972
def extern_entry_points(args, bcs):
9073
new_bcs = []
9174
for bc in bcs:

share/smack/top.py

100755100644
Lines changed: 190 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
import subprocess
77
import signal
88
import functools
9+
import copy
10+
import multiprocessing
11+
import yaml
912
from enum import Flag, auto
1013
from .svcomp.utils import verify_bpl_svcomp
1114
from .utils import temporary_file, try_command, remove_temp_files,\
12-
llvm_exact_bin
15+
llvm_exact_bin, smack_portfolio_path
1316
from .replay import replay_error_trace
1417
from .frontend import link_bc_files, frontends, languages, extra_libs
1518
from .errtrace import error_trace, json_output_str
@@ -536,6 +539,7 @@ def arguments():
536539
choices=[
537540
'boogie',
538541
'corral',
542+
'portfolio',
539543
'symbooglix',
540544
'svcomp'],
541545
default='corral',
@@ -545,6 +549,14 @@ def arguments():
545549
choices=['z3', 'cvc4', "yices2"], default='z3',
546550
help='back-end SMT solver')
547551

552+
verifier_group.add_argument(
553+
'--portfolio-config',
554+
metavar='FILE',
555+
default=smack_portfolio_path(),
556+
action=FileAction,
557+
type=str,
558+
help='read portfolio configuration in YAML format from FILE')
559+
548560
verifier_group.add_argument(
549561
'--unroll',
550562
metavar='N',
@@ -882,8 +894,8 @@ def verification_result(verifier_output, verifier):
882894
line_no = int(boogie_af_msg.group(2))
883895
with open(boogie_af_msg.group(1), 'r') as f:
884896
assert_line = re.search(
885-
attr_pat,
886-
f.read().splitlines(True)[line_no - 1])
897+
attr_pat,
898+
f.read().splitlines(True)[line_no - 1])
887899
if assert_line:
888900
attr = assert_line.group(1)
889901
else:
@@ -901,6 +913,73 @@ def verification_result(verifier_output, verifier):
901913
return VResult.UNKNOWN
902914

903915

916+
def invoke_boogie(args):
917+
command = ["boogie"]
918+
command += [args.bpl_file]
919+
command += ["/doModSetAnalysis"]
920+
command += ["/useArrayTheory"]
921+
command += ["/timeLimit:%s" % args.time_limit]
922+
command += ["/errorLimit:%s" % args.max_violations]
923+
if not args.modular:
924+
command += ["/loopUnroll:%d" % args.unroll]
925+
if args.solver == 'cvc4':
926+
command += ["/proverOpt:SOLVER=cvc4"]
927+
elif args.solver == 'yices2':
928+
command += ["/proverOpt:SOLVER=Yices2"]
929+
return command
930+
931+
932+
def invoke_corral(args):
933+
command = ["corral"]
934+
command += [args.bpl_file]
935+
command += ["/tryCTrace", "/noTraceOnDisk", "/printDataValues:1"]
936+
command += ["/k:%d" % args.context_bound]
937+
command += ["/useProverEvaluate"]
938+
command += ["/timeLimit:%s" % args.time_limit]
939+
command += ["/cex:%s" % args.max_violations]
940+
command += ["/maxStaticLoopBound:%d" % args.loop_limit]
941+
command += ["/recursionBound:%d" % args.unroll]
942+
if args.solver == 'cvc4':
943+
command += ["/bopt:proverOpt:SOLVER=cvc4"]
944+
elif args.solver == 'yices2':
945+
command += ["/bopt:proverOpt:SOLVER=Yices2"]
946+
return command
947+
948+
949+
def invoke_symbooglix(args):
950+
command = ['symbooglix']
951+
command += [args.bpl_file]
952+
command += ["--file-logging=0"]
953+
command += ["--entry-points=%s" % ",".join(args.entry_points)]
954+
command += ["--timeout=%d" % args.time_limit]
955+
command += ["--max-loop-depth=%d" % args.unroll]
956+
return command
957+
958+
959+
def process_verifier_output(args, verifier_output):
960+
verifier_output = transform_out(args, verifier_output)
961+
result = verification_result(verifier_output, args.verifier)
962+
963+
if args.json_file:
964+
with open(args.json_file, 'w') as f:
965+
f.write(json_output_str(result, verifier_output, args.verifier))
966+
967+
if result in VResult.ERROR:
968+
error = error_trace(verifier_output, args.verifier)
969+
970+
if args.error_file:
971+
with open(args.error_file, 'w') as f:
972+
f.write(error)
973+
974+
if not args.quiet:
975+
print(error)
976+
977+
if args.replay:
978+
replay_error_trace(verifier_output, args)
979+
print(result.message(args))
980+
return result.return_code()
981+
982+
904983
def verify_bpl(args):
905984
"""Verify the Boogie source file with a back-end verifier."""
906985

@@ -909,85 +988,140 @@ def verify_bpl(args):
909988
return
910989

911990
elif args.verifier == 'boogie' or args.modular:
912-
command = ["boogie"]
913-
command += [args.bpl_file]
914-
command += ["/doModSetAnalysis"]
915-
command += ["/useArrayTheory"]
916-
command += ["/timeLimit:%s" % args.time_limit]
917-
command += ["/errorLimit:%s" % args.max_violations]
991+
command = invoke_boogie(args)
918992
command += ["/proverOpt:O:smt.array.extensional=false"]
919993
command += ["/proverOpt:O:smt.qi.eager_threshold=100"]
920994
command += ["/proverOpt:O:smt.arith.solver=2"]
921-
if not args.modular:
922-
command += ["/loopUnroll:%d" % args.unroll]
923-
if args.solver == 'cvc4':
924-
command += ["/proverOpt:SOLVER=cvc4"]
925-
elif args.solver == 'yices2':
926-
command += ["/proverOpt:SOLVER=Yices2"]
927995

928996
elif args.verifier == 'corral':
929-
command = ["corral"]
930-
command += [args.bpl_file]
931-
command += ["/tryCTrace", "/noTraceOnDisk", "/printDataValues:1"]
932-
command += ["/k:%d" % args.context_bound]
933-
command += ["/useProverEvaluate"]
934-
command += ["/timeLimit:%s" % args.time_limit]
935-
command += ["/cex:%s" % args.max_violations]
936-
command += ["/maxStaticLoopBound:%d" % args.loop_limit]
937-
command += ["/recursionBound:%d" % args.unroll]
938-
command += ["/bopt:proverOpt:O:smt.qi.eager_threshold=100"]
939-
command += ["/bopt:proverOpt:O:smt.arith.solver=2"]
940-
if args.solver == 'cvc4':
941-
command += ["/bopt:proverOpt:SOLVER=cvc4"]
942-
elif args.solver == 'yices2':
943-
command += ["/bopt:proverOpt:SOLVER=Yices2"]
997+
command = invoke_corral(args)
998+
args.verifier_options += (
999+
" /bopt:proverOpt:O:smt.qi.eager_threshold=100"
1000+
" /bopt:proverOpt:O:smt.arith.solver=2")
9441001

9451002
elif args.verifier == 'symbooglix':
946-
command = ['symbooglix']
947-
command += [args.bpl_file]
948-
command += ["--file-logging=0"]
949-
command += ["--entry-points=%s" % ",".join(args.entry_points)]
950-
command += ["--timeout=%d" % args.time_limit]
951-
command += ["--max-loop-depth=%d" % args.unroll]
1003+
command = invoke_symbooglix(args)
9521004

9531005
if args.verifier_options:
9541006
command += args.verifier_options.split()
9551007

9561008
verifier_output = try_command(command, timeout=args.time_limit)
957-
verifier_output = transform_out(args, verifier_output)
958-
result = verification_result(verifier_output, args.verifier)
1009+
return process_verifier_output(args, verifier_output)
9591010

960-
if args.json_file:
961-
with open(args.json_file, 'w') as f:
962-
f.write(json_output_str(result, verifier_output, args.verifier))
9631011

964-
if result in VResult.ERROR:
965-
error = error_trace(verifier_output, args.verifier)
1012+
def thread_verify_bpl(args, args_to_add):
9661013

967-
if args.error_file:
968-
with open(args.error_file, 'w') as f:
969-
f.write(error)
1014+
if 'verifier' in args_to_add:
1015+
if args_to_add['verifier'] == 'portfolio':
1016+
raise RuntimeError(
1017+
"portfolio is not a valid verifier specification"
1018+
" within the portfolio configuration")
1019+
else:
1020+
print(
1021+
"Warning: SMACK is using argument verifier from"
1022+
" the chosen portfolio configuration")
1023+
args.verifier = args_to_add['verifier']
1024+
else:
1025+
raise RuntimeError(
1026+
"verifier is a required argument"
1027+
" in the portfolio configuration file")
1028+
1029+
if 'modular' in args_to_add:
1030+
if args.modular:
1031+
raise RuntimeError(
1032+
"argument modular specified in both"
1033+
" command line and portfolio configuration")
1034+
else:
1035+
print(
1036+
"Warning: SMACK is using argument modular from"
1037+
" the chosen portfolio configuration")
1038+
args.modular = args_to_add['modular']
1039+
1040+
if (args.verifier != 'boogie') and args.modular:
1041+
raise RuntimeError(
1042+
"Incompatible arguments modular"
1043+
" and non-boogie verifier were specified")
1044+
1045+
if 'verifier-options' in args_to_add:
1046+
if args.verifier_options:
1047+
raise RuntimeError(
1048+
"argument verifier-options specified in both"
1049+
" command line and portfolio configuration")
1050+
else:
1051+
print(
1052+
"Warning: SMACK is using argument verifier-"
1053+
"options from the chosen portfolio configuration")
1054+
args.verifier_options = args_to_add['verifier-options']
1055+
1056+
if 'solver' in args_to_add:
1057+
if args.solver != 'z3': # better check than not default?
1058+
raise RuntimeError(
1059+
"argument solver specified in both command"
1060+
" line and portfolio configuration")
1061+
else:
1062+
print(
1063+
"Warning: SMACK is using argument solver from"
1064+
" the chosen portfolio configuration")
1065+
args.solver = args_to_add['solver']
9701066

971-
if not args.quiet:
972-
print(error)
1067+
if args.verifier == 'boogie' or args.modular:
1068+
command = invoke_boogie(args)
9731069

974-
if args.replay:
975-
replay_error_trace(verifier_output, args)
976-
print(result.message(args))
977-
return result.return_code()
1070+
elif args.verifier == 'corral':
1071+
command = invoke_corral(args)
1072+
1073+
elif args.verifier == 'symbooglix':
1074+
command = invoke_symbooglix(args)
1075+
1076+
if args.verifier_options:
1077+
command += args.verifier_options.split()
1078+
1079+
verifier_output = try_command(command, timeout=args.time_limit)
1080+
return args, verifier_output
1081+
1082+
1083+
def verify_bpl_portfolio(args):
1084+
1085+
portfolio_config = yaml.load(open(args.portfolio_config, 'r'))
1086+
p = multiprocessing.Pool()
1087+
results = {} # map of process -> thread name
1088+
1089+
for thread in list(portfolio_config.keys()):
1090+
async_result = p.apply_async(thread_verify_bpl,
1091+
args=(copy.deepcopy(args),
1092+
portfolio_config[thread]))
1093+
results[async_result] = thread
1094+
1095+
# TODO: revisit this loop to improve efficiency
1096+
while True:
1097+
for result in list(results.keys()):
1098+
if result.ready():
1099+
p.terminate()
1100+
args, verifier_output = result.get()
1101+
verifier_output = process_verifier_output(
1102+
args, verifier_output)
1103+
thread_name = results[result]
1104+
print(f'SMACK portfolio {thread_name} terminated')
1105+
return verifier_output
9781106

9791107

9801108
def clean_up_upon_sigterm(main):
9811109
def handler(signum, frame):
1110+
remove_temp_files_lock.acquire()
9821111
remove_temp_files()
1112+
remove_temp_files_lock.release()
9831113
sys.exit(0)
1114+
9841115
signal.signal(signal.SIGTERM, handler)
9851116
return main
9861117

9871118

9881119
@clean_up_upon_sigterm
9891120
def main():
9901121
try:
1122+
global remove_temp_files_lock
1123+
remove_temp_files_lock = multiprocessing.Lock()
1124+
9911125
global args
9921126
args = arguments()
9931127

@@ -1002,7 +1136,10 @@ def main():
10021136
if not args.quiet:
10031137
print("SMACK generated %s" % args.bpl_file)
10041138
else:
1005-
return_code = verify_bpl(args)
1139+
if args.verifier == 'portfolio':
1140+
return_code = verify_bpl_portfolio(args)
1141+
else:
1142+
return_code = verify_bpl(args)
10061143
sys.exit(return_code)
10071144

10081145
except KeyboardInterrupt:

0 commit comments

Comments
 (0)