[PATCH] GCC 16.2 Ada: sửa lỗi dọn dẹp đối tượng protected khi Lock_Free được chọn tự động
Tóm tắt
Tôi đã tái hiện một lỗi ở front-end Ada của GCC/GNAT 16.2.0 trên FreeBSD 15.1 amd64. Lỗi này khiến bộ nhớ tiến trình tăng không giới hạn khi các đối tượng protected đơn giản, có vòng đời ngắn, được tạo lặp đi lặp lại.
Một kiểu protected đủ điều kiện cho cơ chế Lock_Free tự động của GNAT có thể
được mở rộng thành biểu diễn chứa trường _object native kiểu
System.Tasking.Protected_Objects.Protection và Initialize_Protection, rồi chỉ
sau đó mới được đánh dấu là lock-free trong quá trình phân tích protected body.
Việc sinh mã dọn dẹp sau đó dựa vào trạng thái Uses_Lock_Free được xác định
muộn hơn và bỏ qua Finalize_Protection, dù _object native đã được tạo
và khởi tạo.
Đây là lỗi ở trình biên dịch GCC upstream, nhưng hệ quả runtime có thể quan sát được đã được tái hiện trên FreeBSD. Một bản vá downstream nhỏ theo kiểu FreeBSD ports được cung cấp ở đây để một port GCC 16.2 có bật Ada có thể tích hợp bản sửa mà không cần chờ hành động từ upstream.
Phạm vi port FreeBSD
Trình biên dịch được kiểm thử là một bản dựng GCC 16.2.0 có bật Ada, dựa trên cấu hình và bộ bản vá của port GCC trên FreeBSD.
Port lang/gcc16 chính thức hiện tại không bật front-end Ada, vì vậy tôi không
khẳng định rằng chính gói nhị phân lang/gcc16 hiện tại bị ảnh hưởng. Các port
Ada của FreeBSD dựa trên những bản GCC cũ hơn không được khảo sát trong báo cáo
này và tôi không đưa ra khẳng định nào về chúng.
Các bản vá port GCC của FreeBSD dùng cho baseline GCC 16.2 không sửa ba file
nguồn GCC Ada liên quan đến lỗi này (exp_ch7.adb, exp_ch9.adb,
sem_ch9.adb).
Triệu chứng của trình biên dịch có thể tái hiện nhất quán
Với GCC/GNAT 16.2.0, trường hợp mặc định được chọn tự động tạo ra kết quả sau:
case Initialize Finalize lock-free ops Protection field
default (automatic) yes no yes present
Lock_Free => True no no yes absent
Lock_Free => False yes yes no present
Trường hợp mặc định không nhất quán chính là lỗi.
Mã nguồn tối thiểu:
procedure Protected_Finalize is
protected type Prot is
procedure Touch;
private
Value : Integer := 0;
end Prot;
protected body Prot is
procedure Touch is
begin
Value := Value + 1;
end Touch;
end Prot;
Object : Prot;
begin
Object.Touch;
end Protected_Finalize;
Biên dịch bằng:
gcc16 -c -O0 -fdump-tree-original protected_finalize.adb
Tree dump của GCC 16.2 chưa sửa có
system.tasking.protected_objects.initialize_protection nhưng không có
system.tasking.protected_objects.finalize_protection.
Hệ quả runtime trên FreeBSD
Với workload độc lập gồm 50 batch, mỗi batch 100 request, baseline GCC/GNAT 16.2.0 cho kết quả:
case constructions RSS growth VSZ growth
plain_eight 0 +4 KiB 0 KiB
direct_protected_one 5,000 +628 KiB +528 KiB
protected_one 5,000 +628 KiB +528 KiB
protected_eight 40,000 +5012 KiB +5016 KiB
lock_free_eight 40,000 +4 KiB 0 KiB
Số thread và file descriptor vẫn ổn định. Trên FreeBSD, luồng xử lý của đối tượng
protected native cuối cùng sử dụng pthread_mutex_init / pthread_mutex_destroy,
vì vậy việc thiếu phần finalization do trình biên dịch sinh ra biểu hiện trực tiếp qua
mức tăng bộ nhớ dành cho trạng thái mutex native.
Nguyên nhân gốc
Thứ tự xử lý của front-end GCC 16.2 là:
Expand_N_Protected_Type_Declaration
-> Uses_Lock_Free is still False
-> generate native Protection _object
Build_Record_Init_Proc
-> generate Initialize_Protection
Analyze_Protected_Body
-> automatically Set_Uses_Lock_Free
cleanup generation
-> Is_Simple_Protected_Type tests not Uses_Lock_Free
-> skip existing Finalize_Protection path
Trace LLDB xác nhận rằng, với chương trình tái hiện này, Make_Initialize_Protection
được thực thi trước Set_Uses_Lock_Free diễn ra sau đó.
Bản sửa
Bản sửa làm cho Is_Simple_Protected_Type phân loại kiểu dựa trên biểu diễn đã
thực sự được mở rộng thay vì cờ Uses_Lock_Free có thể thay đổi về sau.
Nó kiểm tra record tương ứng đã được phân tích và có chứa _object, rồi tái sử
dụng Find_Protection_Type để xác nhận trường đó có kiểu
System.Tasking.Protected_Objects.Protection thông thường.
Điều này giữ nguyên hành vi của Lock_Free => True được chỉ định rõ ràng vì
biểu diễn đó không có trường _object, đồng thời giữ các kiểu protected có entry
trên đường xử lý hiện có của chúng.
Bản vá theo kiểu FreeBSD ports được đưa vào bên dưới:
patch-gcc_ada_exp__ch7.adb
Có thể áp dụng từ thư mục gốc của mã nguồn GCC 16.2 bằng patch -p0.
Xác minh bản vá
Phần thay đổi chức năng chính xác trong bản vá FreeBSD đã được biên dịch và kiểm thử trong một cây compiler GCC/GNAT 16.2 được cô lập.
Kết quả sau khi vá:
default (automatic) Initialize=yes Finalize=yes lock-free=yes
Lock_Free => True Initialize=no Finalize=no lock-free=yes
Lock_Free => False Initialize=yes Finalize=yes lock-free=no
Trong cùng workload, phép chạy runtime độc lập trên FreeBSD không cho thấy mức tăng tỷ lệ với số lần tạo đối tượng ở bất kỳ trường hợp nào:
plain_eight RSS +4 KiB, VSZ 0 KiB
direct_protected_one RSS +4 KiB, VSZ 0 KiB
protected_one RSS +4 KiB, VSZ 0 KiB
protected_eight RSS +4 KiB, VSZ 0 KiB
lock_free_eight RSS +4 KiB, VSZ 0 KiB
Một tập kiểm thử hồi quy GCC Ada DejaGNU được chọn đã hoàn tất với 20 test pass đúng như dự kiến, 0 failure ngoài dự kiến và 0 test unresolved. Các phép kiểm tra bổ sung cho thấy finalization tương ứng đã được khôi phục cho protected subtype trong trường hợp chọn tự động, array và heap object; tree dump của một kiểu protected có entry giống hệt từng byte trước và sau bản vá.
Hành động đề nghị FreeBSD
Vui lòng cân nhắc đưa bản vá mã nguồn được cung cấp vào một port GCC 16.2 có bật Ada, chẳng hạn một port GNAT 16 trong tương lai hoặc tương đương, cho đến khi vấn đề được sửa trong mã nguồn trình biên dịch mà port đó sử dụng.
Tôi có thể cung cấp chương trình tái hiện runtime độc lập, số đo thô, tree dump của trình biên dịch, trace thứ tự bằng LLDB và các bản tóm tắt DejaGNU nếu chúng hữu ích cho việc đánh giá. Toàn bộ bằng chứng được lưu giữ cục bộ; bản vá mã nguồn nhỏ và báo cáo này được thiết kế để đủ cho đợt đánh giá ban đầu của FreeBSD.
Phương án Bugzilla nếu maintainer yêu cầu
Kênh thông thường của FreeBSD để gửi bản vá cho một port hiện có là Bugzilla. Nếu các maintainer Ada muốn theo dõi vấn đề tại đó sau khi chọn port bị ảnh hưởng hoặc port mục tiêu, hãy dùng:
Product: Ports & Packages
Component: Individual Port(s)
Summary: [PATCH] lang/<maintainer-selected-port>: fix GCC 16.2 Ada protected cleanup
Đính kèm patch-gcc_ada_exp__ch7.adb ở dạng không nén. Không thay bằng
lang/gcc16 chỉ để điền trường này: bản dựng chính thức hiện tại của nó không
bật Ada. Hãy dùng đúng port mục tiêu do maintainer FreeBSD lựa chọn.
patch-gcc_ada_exp__ch7.adb:
--- gcc/ada/exp_ch7.adb.orig
+++ gcc/ada/exp_ch7.adb
@@ -5432,12 +5432,34 @@
------------------------------
function Is_Simple_Protected_Type (T : Entity_Id) return Boolean is
+ Comp : Entity_Id;
+ Rec_Typ : Entity_Id;
+
begin
- return
- Is_Protected_Type (T)
- and then not Uses_Lock_Free (T)
- and then not Has_Entries (T)
- and then Is_RTE (Find_Protection_Type (T), RE_Protection);
+ if not Is_Protected_Type (T) or else Has_Entries (T) then
+ return False;
+ end if;
+
+ Rec_Typ := Corresponding_Record_Type (T);
+
+ if No (Rec_Typ) or else not Analyzed (Rec_Typ) then
+ return False;
+ end if;
+
+ -- Base the decision on the representation that was actually built.
+ -- Automatic lock-free selection may happen after the corresponding
+ -- record and its initialization procedure have already been created.
+
+ Comp := First_Component (Rec_Typ);
+ while Present (Comp) loop
+ if Chars (Comp) = Name_uObject then
+ return Is_RTE (Find_Protection_Type (T), RE_Protection);
+ end if;
+
+ Next_Component (Comp);
+ end loop;
+
+ return False;
end Is_Simple_Protected_Type;
-------------------------------