Theo [10] hiện nay nhiều vấn đề thuộc trí tuệ nhân tạo và phương pháp hình thức được giải quyết bằng cách sử dụng các SMT solver. Việc lựa chọn một SMT solver tốt nhất cho một chương trình nhất định cho trước là điều hết sức cần thiết. Nhưng lại là một nhiệm vụ hết sức khó khăn với 2 lý do:
1. Bài toán SAT là NP – đầy đủ, hiệu quả cũng như sức mạnh của SMT solver phụ thuộc vào cách tiếp cận dựa trên kinh nghiệm. Vì vậy, mỗi SMT solver có điểm mạnh và điểm yếu riêng của nó và do đó thời gian thực hiện cũng như bộ nhớ tiêu thụ cũng khác nhau tùy thuộc vào khả năng của các SMT solver cho từng vấn đề cụ thể.
2. Các SMT solver khác nhau không sử dụng chung một định dạng đầu vào. SMT – LIB [20] sau này cải tiến thành SMT – LIB2 [2] là định dạng chuẩn mà nhiều SMT solver sử dụng. Tuy nhiên, không phải toàn bộ các SMT solver đều sử dụng chuẩn đầu vào này.
MetaSMT [8, 10] là giải pháp cho vấn đề này. Nó cung cấp một khung làm việc cho phép tích hợp nhiều SMT solver trong các ứng dụng C, C++, Python. Với mục đích cung cấp một giao diện chung thống nhất cho các SMT
solver khác nhau, sử dụng ngôn ngữ C/C++, khả mở với các lý thuyết cũng như các API của solver mới, dễ dàng tùy chỉnh với từng mục đích cụ thể. Nó sử dụng và phát triển dựa trên chuẩn SMT – LIB2. Kiến trúc của metaSMT thể hiện trong hình 4.2.
Hình 4.2 Kiến trúc của metaSMT [10]
Kiến trúc của metaSMT gồm ba tầng: FrontEnd, MiddleEnd, và BackEnd. Với ba tầng này metaSMT đã định nghĩa một chuẩn đầu vào chung cho nhiều SMT solver.
a) Tầng FrontEnd: Là tầng cung cấp ngôn ngữ đầu vào cho metaSMT. Nó
cung cấp các lý thuyết được hỗ trợ bởi định dạng SMT – LIB2 dưới dạng một API của ngôn ngữ lập trình. Gồm lý thuyết về các thao tác Logic cơ bản (Core), Bit – Vector với số Bit cố định cho trước (FixedSize Bit Vector) và lý thuyết mảng mở rộng (ArrayEx). Trong đó cấu trúc của từng thành phần được trình bày dưới đây:
Core (Logic cơ bản): Bao gồm các vị từ, các biến với kiểu trả về
boolean (với hai giá trị là đúng (true) hoặc sai (false)) và các thao tác trên biến chẳng hạn and (), or (), not (), các phép so sánh bằng (), phép so sánh khác ().
FixedSize Bit vector (Bit – Vector số Bit cố định cho trước): Bao gồm các phép toán tạo mới một Bit – Vector với n bit
meta
S
M
T
FrontEnd
Core FixedSize Bit vector ArrayEx
MiddleEnd
Direct Solver Graph Solver Bit Blast
BackEnd
Z3 Boolector STP MiniSAT …
(new_bitvector (n)), các phép Logic trên bit như and (bvand), or (bvor), not (bvnot) hay các phép toán trên Bit – Vector chẳng hạn phép cộng (bvadd), phép trừ (bvsub), phép nhân (bvmul), … Ngoài ra, Bit – Vector cũng định nghĩa một số các phép khác như phép so sánh bằng (equal) hoặc so sánh khác (less - than).
ArrayEx (Lý thuyết mở rộng của mảng): Bao gồm các thao tác trên các phần tử của mảng hoặc các thao tác trên chỉ số mảng.
Hiện tại, các ngôn ngữ lập trình hỗ trợ lý thuyết về thao tác Logic cơ bản (Logic Core Boolean), Bit – Vector với kích thước Bit cố định cho trước và lý thuyết mảng mở rộng là C/C++ và Python.
b) Tầng MiddleEnd: Là tầng trung gian kết nối giữa FrontEnd với
BackEnd. Nó cung cấp đầu vào chuẩn cho các SMT solver trong Backend và tối ưu hóa. Chẳng hạn sự kết hợp các lý thuyết được cung cấp bởi FrontEnd với GraphSolver trong tầng MiddleEnd tạo ra đầu vào chuẩn cho SMT solver Z3 ở tầng BackEnd. Hai thành phần chính được sử dụng trong middleend là DirectSolver và GraphSolver.
Director Solver: Cho phép biên dịch trực tiếp từ một FronEnd và BackEnd. Nghĩa là biểu thức đầu vào sẽ được đưa trực tiếp vào BackEnd và được đánh giá bởi thành phần này. Chẳng hạn với SMT solver STP có thể dùng trực tiếp đầu vào ở FrontEnd mà không cần thông qua MiddleEnd hay một biểu thức dạng CNF là đầu vào trực tiếp của các SAT solver như miniSAT.
Graph Solver: Thay vì đưa trực tiếp biểu thức ở FrontEnd vào BackEnd để giải thì trong trường hợp này đưa vào một đồ thị có hướng. Biểu thức đầu vào được đưa vào dưới dạng một đồ thị có hướng không chứa chu trình. Trong đó, mỗi nút của đồ thị này là toán hoạng của biểu thức đầu vào hoặc là một giá trị cụ thể được gán cho các toán hạng của biểu thức. Mỗi cạnh của đồ thị là một biểu thức gán giá trị cho các toán hạng của biểu thức đầu vào. Hình
4.3 minh họa một biểu thức đầu vào dưới dạng một đồ thị có hướng không chứa chu trình.
Hình 4.3. Biểu thức đầu vào dạng đồ thị [8]
Bit Blast: Là hình thức mô phỏng đầu vào dưới dạng Bit – Vector
(QF – BV). Trong đó mỗi biến Logic được biểu diễn bằng một Bit – Vector. Các phép toán trên Bit có thể được thực hiện dễ dàng.
Mỗi phép so sánh giữa hai Bit – Vector được thể hiện bằng các mệnh đề (equal, less – than, …).
c) Tầng Backend: Tầng này cung cấp hai giao diện khác nhau cho cho SAT
và SMT solver. Giao diện đầu tiên là một ánh xạ tĩnh từ API metaSMT vào các API của các SMT và SAT solver. Giao diện thứ hai là luồng chung với đầu vào dạng SMT –LIB2 và các solver tương thích với nó. MetaSMT quy ước sự kết hợp của một thành phần ở MiddleEnd với một thành phần tương ứng với nó trong BackEnd được gọi một context.
Trên đây là kiến trúc tổng quan và các thành phần của metaSMT. Tiếp theo luận văn sẽ trình bày về quá trình giải ràng buộc của các solver trong metaSMT.
Quá trình giải ràng buộc của metaSMT xem hình 4.4. Quá trình thực hiện của metaSMT gồm 2 pha cơ bản. Pha 1 là quá trình phân tích cú pháp biểu thức dạng SMT – LIB2 thông qua bộ phân tích cú pháp SMT – LIB2 và đánh giá biểu
x0
x1 1
0 x
2
thức thông qua bộ đánh giá chung. Pha 2 là quá trình giải với các solver của metaSMT. Pha này tiến hành đọc các lệnh dạng SMT – LIB2 ở đầu vào và đánh giá các biểu thức này. Sau đó tiến hành giải với các solver và chọn ra solver thực hiện tốt nhất.
Hình 4.4. Mô hình quá trình giải ràng buộc của metaSMT [10]
Một ca kiểm thử là tốt nếu độ bao phủ [14] (coverage) mã nguồn của nó là cao. Một công cụ sinh ca kiểm thử tự động được đánh giá là tốt nếu các ca kiểm thử mà nó sinh ra đủ tốt. Theo [4] công cụ KLEE được đánh giá là có độ bao phủ mã trung bình hơn 90%. Các kiến thức cơ bản về độ bao phủ mã nguồn và tiêu chí chọn các đường thực thi để đạt được độ bao phủ mã cao được trình bày trong 4.2 của luận văn.