Phân tích quay lại (backward)

Một phần của tài liệu Nghiên cứu kỹ thuật phân tích chương trình tĩnh trong việc nâng cao chất lượng phần mềm (Trang 25)

2 Phân tích chương trình tĩnh

2.1.1Phân tích quay lại (backward)

Với mỗi điểm của chương trình (nút trên CFG), phân tích quay lại (backward analysis) là phân tích thông tin về hành vi trong tương lai. Do đó, trong vế phải của các phương trình chỉ phụ thuộc vào các nút kế sau nó (successor) trên CFG. Phân tích lùi được bắt đầu từ nútexitcủa CFG và di chuyển quay lại trong CFG. Một số phân tích điển hình: Phân tíchtính sống của biến (Liveness), phân tích biểu thức bận rộn(Busy Expression)

Phân tích tính sống của biến

Trong một chương trình việc xác định tính sống của biến tại mỗi điểm của chương trình là rất cần thiết, việc này giúp chương trình xác định và loại bỏ được các biến chết giúp tối ưu hóa bộ nhớ/ tối ưu hoá chương trình dịch và làm tăng tốc độ tính toán của chương trình. Một biến được gọi là biến sống tại một điểm của chương trình (liveness) nếu giá trị hiện tại của nó được đọc trong nút hiện tại hoặc được đọc trong một số nút kế tiếp (không được ghi ở nút hiện tại). Thuộc tính này có thể được xấp xỉ bởi phân tích tĩnh dựa trên CFG với một dàn là một tập các biến trong chương trình.

Gọi các biến ràng buộc cho mỗi nút v trên CFG là JvK, khi đó các ràng buộc cho tính sống của biến tại các nút với các cấu trúc lệnh trong chương trình được xác định như sau:

• Đối với nút exit, có ràng buộc là:

JexitK= ∅

• Đối với những lệnh điều kiện, lệnh return và lệnh printf(E), ràng buộc là:

JvK= ∪

• Đối với những phép gán id = E, ràng buộc là:

JvK= ∪

wsucc(v)JwK\ {id} ∪vars(E)

• Đối với một khai báo biến intid1, ...,idn, ràng buộc là:

JvK= ∪

wsucc(v)JwK\ {id1, ...,idn} • Cuối cùng, đối với những nút khác, ràng buộc là:

JvK= ∪

wsucc(v)JwK

Hàmvars(E)là tập các biến trongE và vế phải của các ràng buộc trên là các hàm đơn điệu.

Với quan sát trên CFG của một chương trình, một biến là sống nếu nó được đọc trong nút hiện tại, hoặc nó được đọc trong một số nút kế tiếp trừ khi nó được ghi ở nút hiện tại.

Ví dụ phân tích tính sống của biến cho chương trình trong Mục 1.4.1. Trong trường hợp này, tính lặp của hàm tính giai thừa được sử dụng và CFG liên quan của nó được thể hiện trong Hình 1.5.

Dàn ở đây làL = (2{f, uu_f, n},⊆), biểu diễn bằng biểu đồ Hasse:

{f,uu_f,n}

{f}

{}

{uu_f} {n}

{f,uu_f} {f,n} {uu_f,n}

Hình 2.1: Dàn cho chương trình phân tích tính sống của biến.

JentryK = Jint fK

Jint fK = Jint uu_fK\ {f}

Jint uu_fK= Jf = 1K\ {uu_f}

Jf = 1K = Juu_f = 0K\ {f} Juu_f = 0K= Jn > 0K\ {uu_f} Jn > 0K = (Jreturn fK∪Jf = f*nK)∪ {n} Jreturn fK= JexitK∪ {f} Jf = f*nK= (Jn = n-1K\ {f})∪ {n,f} Jn = n-1K= (Jn > 0K\ {n})∪ {n} JexitK = ∅

Hệ phương trình trên là để giải thông qua Dàn: L = (2{f, uu_f, n},⊆). Hơn nữa, nó dễ dàng thấy rằng tất cả vế phải ràng buộc của phương trình định nghĩa những hàm đơn điệu. Theo kết quả, lý thuyết của điểm cố định nhỏ nhất có thể được áp dụng [16]. Nghiệm nhỏ nhất cho hệ phương trình (Được giải trong Phụ lục A) là duy nhất là: JentryK = ∅ Jint fK = ∅ Jint uu_fK= ∅ Jf = 1K = {n} Juu_f = 0K= {n,f} Jn > 0K = {n,f} Jreturn fK= {f} Jf = f*nK= {n,f} Jn = n-1K= {n,f} JexitK = ∅ (adsbygoogle = window.adsbygoogle || []).push({});

Từ những thông tin này một bộ dịch có thể suy ra rằnguu_fchưa bao giờ sống, và giá trị được ghi trong phép gánuu_f = 0chưa bao giờ được đọc. Do đó, chương trình hàm lặp trong Mục 1.4.1 có thể chạy an toàn và tối ưu hóa như sau:

int iterative(int n){

int f;// khai báo biến (f có kiểu là int) f = 1;

f = f*n; n = n - 1;

} }

việc loại bỏ biến chết giúp tiết kiệm chi phí của phép gán và khai báo biến cho hàm đơn giản. Do đó, kỹ thuật phân tích này cho kết quả tốt hơn trong việc cấp phát bộ nhớ thanh ghi. Ngoài ra, đối với những chương trình phức tạp hơn thì lợi ích từ việc phân tích này có thể rất lớn trong tính toán của bộ nhớ và tối ưu hóa trong xử lý thông qua bộ chương trình dịch thông minh.

Phân tích biểu thức bận rộn

Việc tính toán các biểu thức trong chương trình làm tăng bộ nhớ và làm chậm thời gian chạy kết quả của chương trình. Do vậy, trong chương trình nếu hạn chế tính toán lại một biểu thức trong tương lai sẽ giúp chương trình chạy nhanh hơn và giúp bộ nhớ tối ưu hơn.

Một biểu thức E là bận rộn (busy) tại điểm p của chương trình nếu tất cả các đường (path) xuất phát từ một điểm chương trình p phải được đánh giá E trước khi một biến bất kỳ trong biểu thức E thay đổi. Hoặc, ta cũng có thể hiểu là giá trị của biểu thức được đánh giá tại thời điểm hiện tại hoặc sẽ được đánh giá trong tất cả các nút trong tương lai trừ khi một phép gán làm thay đổi giá trị của nó. Để xấp xỉ thuộc tính này, ta cần Dàn là tập các biểu thức trong chương trình. Đối với phân tích này, ta xác định được các ràng buộc luồng dữ liệu cho các cấu trúc lệnh như sau:

• Ràng buộc cho lệnh exit:

JexitK = {}

• Các ràng buộc cho các lệnh điều kiện và output là:

JvK= ∩

wsucc(v)JwK∪exps(E)

• Ràng buộc cho các phép gán là:

JvK = ∩

• Với tất cả các nút còn lại có ràng buộc là: JvK= ∩ wsucc(v)JwK Ta xét ví dụ sau: int func(int x){ int a,b; a = x-1; b = x-2; while (x > 0){ return a*b-x; x = x-1; } return a*b; }

Dàn cho phân tích biểu thức bận rộn của chương trình ví dụ này là:

L= (2{x-1,x-2,x>0,a*b-x,a*b},⊆) {x-1,x-2,x>0} {x-1,x-2,x>0,a*b-x,a*b} {x-1} {a*b-x} {} {a*b} {x-2} {x>0} ... ... ... ... . . . . . . . . . .

Hình 2.2:Dàn cho chương trình phân tích biểu thức bận rộn.

b=x-2 return a*b-x a=x-1 x > 0 int a,b x=x-1 return a*b

Hì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: 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 = ∅ (adsbygoogle = window.adsbygoogle || []).push({});

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ứcablà 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; }

Một phần của tài liệu Nghiên cứu kỹ thuật phân tích chương trình tĩnh trong việc nâng cao chất lượng phần mềm (Trang 25)