2 Phân tích chương trình tĩnh
2.3 CFG của chương trình phân tích biểu thức bận rộn
Các ràng buộc:
JentryK = Jint a,bK Jint a,bK= Ja = x-1K
Ja = x-1K= (Jb = x-2K) ↓ a∪ {x-1}
Jb = x-2K= (Jx > 0K) ↓ b∪ {x-2}
Jx > 0K = (Jreturn a*b-xK∩Jreturn a*bK)∪ {x>0}
Jreturn a*b-xK= Jx = x-1K∪ {a*b-x}
Jx = x-1K= (Jx>0K) ↓ x∪ {x-1}
Jreturn a*bK= JexitK∪ {a*b}
JexitK = ∅
Giải hệ phương trình ràng buộc trên (Phụ lục B), ta đươc nghiệm nhỏ nhất là:
JentryK = {x-2,x-1,x>0}
Ja = x-1K= {x>0,x-2,x-1}
Jb = x-2K= {x>0,x-2}
Jx > 0K = {a*b,x>0}
Jreturn a*b-xK= {x-1,a*b,a*b-x}
Jx = x-1K= {a*b,x-1}
Jreturn a*bK= {a*b}
JexitK = ∅
Các phân tích cho thấy một biểu thứca∗blà rất bận rộn bên trong vòng lặp. Trình biên dịch có thể thực hiện cải tiến mã nguồn và chuyển các tính toán đến điểm chương trình sớm nhất mà nó là rất bận rộn. Điều này sẽ làm thay đổi chương trình trở nên hiệu quả hơn:
int func(int x){ int a,b,t; a = x-1; b = x-2; t = a*b; while (x>0){ return t-x; x = x-1; } return t; }
2.1.2. Phân tích chuyển tiếp (forward)
Với mỗi điểm của chương trình (nút trên CFG), phân tích chuyển tiếp (forward analysis) là phân tích thông tin về hành vi trong quá khứ. Do đó, trong vế phải của các phương trình chỉ phụ thuộc vào các nút kế trước nó (predecessor) trên CFG. Phân tích tiến được bắt đầu từ nút entry của CFG và di chuyển chuyển tiếp trong CFG. Một số phân tích điển hình: Phân tíchbiểu thức có sẵn(Available Expression), phân tíchđịnh nghĩa tới được(Reaching Denitions)
Phân tích biểu thức có sẵn
Một biểu thức không bình thường (nontrivial) trong một chương trình là có sẵn (available) tại một điểm trong chương trình nếu giá trị của nó đã được tính toán sẵn trước đó trong khi thực thi. Việc xác định các biểu thức đã có sẵn trước khi thực thi sẽ giúp cho việc tính toán nhanh và đơn giản hơn. Do vậy, trong phân tích này chúng ta sử dụng các thông tin về hành vi trong quá khứ. Và, Dàn cho phân tích này là tập hợp các biểu thức xảy ra cho tất cả các điểm chương trình và được sắp bởi các tập con đảo ngược (reverse). Đối với mỗi nút v trên CFG tương ứng với một biến ràng buộc JvK trên Dàn L chứa các tập con của các biểu thức mà nó được đảm bảo luôn luôn có sẵn tại điểm chương trình kế sau nút đó. Ví dụ, biểu thứca+b là có sẵn ở điều kiện trong vòng lặp, nhưng nó không phải là có sẵn tại các phép gán trong vòng lặp. Phân tích đưa ra sẽ bảo toàn kể từ khi thiết lập tính toán có thể là quá nhỏ. Từ đó, có các ràng buộc luồng dữ liệu cho các cấu trúc lệnh trong phân tích như sau:
• Với mỗi nút entryta có ràng buộc:
JentryK = {}
• Nếuv chứa một điều kiệnE hoặc lệnh output E, khi đó ràng buộc là:
JvK= ∩
w∈pred(v)JwK∪exps(E)
• Nếuv chứa một phép gánid = E, khi đó ràng buộc là:
JvK = ( ∩
w∈pred(v)JwK∪exps(E)) ↓ id
• Đối với tất cả các lệnh khác của các nút, có ràng buộc là:
JvK= ∩
Với hàm ↓ id loại bỏ tất cả các biểu thức có chứa một tham chiếu đến biến id, và các hàmexpsđược định nghĩa là:
exps(intconst) = ∅
exps(id) = ∅
exps(input) = ∅
exps(E1opE2) = {E1opE2} ∪exps(E1)∪exps(E2)
Vớiop là phép toán nhị phân bất kỳ. Ta thấy rằng một biểu thức là có sẵn trong v
nếu nó có sẵn từ tất cả các cạnh hoặc được tính toán trong nútv, trừ khi giá trị của nó đã được hủy bởi lệnh gán. Một lần nữa, phía vế phải của những ràng buộc là những hàm đơn điệu. Ví dụ: int func(){ int x,y,a,b; y = a-b; while (y < a+b){ a = a-1; x = a+b; } return x; }
Ta có 4 biểu thức khác nhau, do đó Dàn cho phân tích với chương trình là:
{a+b}
{}
{a-b} {y<a+b} {a-1}
{a+b,y<a+b} {a+b,a-1} {a-b,y<a+b} {a-b,a-1} {y<a+b,a-1} {a+b,a-b}
{a+b,a-b,y<a+b} {a+b,a-b,a-1} {a+b,y<a+b,a-1} {a-b,y<a+b,a-1}
{a+b,a-b,y<a+b,a-1}