| ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 |
| ; RUN: opt -S -passes="default<O3>" < %s | FileCheck %s |
| |
| ; FIXME: Mirrors missing optimization on empty std::set |
| ; The constructor stores null, so the destructor's erase call |
| ; should be simplified to a no-op, making the entire function ret void. |
| |
| ; Test that the CGSCC inliner forwards stores to load arguments after |
| ; inlining. This addresses a phase ordering issue: the constructor is |
| ; inlined first (producing stores), and then the destructor's load |
| ; arguments should see the stored constants. Without this forwarding, |
| ; the recursive erase function appears too expensive to inline. |
| ; |
| ; This models the std::set<int>{} pattern where the constructor stores |
| ; null to the root pointer and the destructor checks it before recursive |
| ; tree deletion (_Rb_tree::_M_erase). |
| |
| target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" |
| target triple = "x86_64-unknown-linux-gnu" |
| |
| declare void @free(ptr) |
| declare void @use(i64) |
| |
| ; Models _Rb_tree constructor: stores null root + zero node count. |
| define internal void @ctor(ptr %this) { |
| store ptr null, ptr %this, align 8 |
| %count = getelementptr inbounds i8, ptr %this, i64 8 |
| store i64 0, ptr %count, align 8 |
| ret void |
| } |
| |
| ; Models ~_Rb_tree: loads root and calls the recursive eraser. |
| define internal void @dtor(ptr %this) { |
| %root = load ptr, ptr %this, align 8 |
| call void @erase(ptr %root) |
| ret void |
| } |
| |
| ; Models _Rb_tree::_M_erase — recursive node deletion. |
| ; With null: icmp + branch to done → trivially cheap. |
| ; With unknown ptr: recursive calls + external calls → too expensive. |
| define internal void @erase(ptr %node) { |
| ; CHECK-LABEL: define internal fastcc void @erase( |
| ; CHECK-SAME: ptr captures(address_is_null) [[NODE:%.*]]) unnamed_addr { |
| ; CHECK-NEXT: [[IS_NULL:%.*]] = icmp eq ptr [[NODE]], null |
| ; CHECK-NEXT: br i1 [[IS_NULL]], label %[[COMMON_RET1:.*]], label %[[RECURSE:.*]] |
| ; CHECK: [[COMMON_RET1]]: |
| ; CHECK-NEXT: ret void |
| ; CHECK: [[RECURSE]]: |
| ; CHECK-NEXT: [[LEFT_VAL:%.*]] = load ptr, ptr [[NODE]], align 8 |
| ; CHECK-NEXT: tail call fastcc void @erase(ptr [[LEFT_VAL]]) |
| ; CHECK-NEXT: [[RIGHT_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[NODE]], i64 8 |
| ; CHECK-NEXT: [[RIGHT_VAL:%.*]] = load ptr, ptr [[RIGHT_PTR]], align 8 |
| ; CHECK-NEXT: tail call fastcc void @erase(ptr [[RIGHT_VAL]]) |
| ; CHECK-NEXT: [[D1:%.*]] = getelementptr inbounds nuw i8, ptr [[NODE]], i64 16 |
| ; CHECK-NEXT: [[V1:%.*]] = load i64, ptr [[D1]], align 8 |
| ; CHECK-NEXT: tail call void @use(i64 [[V1]]) |
| ; CHECK-NEXT: [[D2:%.*]] = getelementptr inbounds nuw i8, ptr [[NODE]], i64 24 |
| ; CHECK-NEXT: [[V2:%.*]] = load i64, ptr [[D2]], align 8 |
| ; CHECK-NEXT: tail call void @use(i64 [[V2]]) |
| ; CHECK-NEXT: [[D3:%.*]] = getelementptr inbounds nuw i8, ptr [[NODE]], i64 32 |
| ; CHECK-NEXT: [[V3:%.*]] = load i64, ptr [[D3]], align 8 |
| ; CHECK-NEXT: tail call void @use(i64 [[V3]]) |
| ; CHECK-NEXT: [[D4:%.*]] = getelementptr inbounds nuw i8, ptr [[NODE]], i64 40 |
| ; CHECK-NEXT: [[V4:%.*]] = load i64, ptr [[D4]], align 8 |
| ; CHECK-NEXT: tail call void @use(i64 [[V4]]) |
| ; CHECK-NEXT: [[D5:%.*]] = getelementptr inbounds nuw i8, ptr [[NODE]], i64 48 |
| ; CHECK-NEXT: [[V5:%.*]] = load i64, ptr [[D5]], align 8 |
| ; CHECK-NEXT: tail call void @use(i64 [[V5]]) |
| ; CHECK-NEXT: tail call void @free(ptr nonnull [[NODE]]) |
| ; CHECK-NEXT: br label %[[COMMON_RET1]] |
| ; |
| %is_null = icmp eq ptr %node, null |
| br i1 %is_null, label %done, label %recurse |
| |
| recurse: |
| %left_val = load ptr, ptr %node, align 8 |
| call void @erase(ptr %left_val) |
| %right_ptr = getelementptr inbounds i8, ptr %node, i64 8 |
| %right_val = load ptr, ptr %right_ptr, align 8 |
| call void @erase(ptr %right_val) |
| %d1 = getelementptr inbounds i8, ptr %node, i64 16 |
| %v1 = load i64, ptr %d1, align 8 |
| call void @use(i64 %v1) |
| %d2 = getelementptr inbounds i8, ptr %node, i64 24 |
| %v2 = load i64, ptr %d2, align 8 |
| call void @use(i64 %v2) |
| %d3 = getelementptr inbounds i8, ptr %node, i64 32 |
| %v3 = load i64, ptr %d3, align 8 |
| call void @use(i64 %v3) |
| %d4 = getelementptr inbounds i8, ptr %node, i64 40 |
| %v4 = load i64, ptr %d4, align 8 |
| call void @use(i64 %v4) |
| %d5 = getelementptr inbounds i8, ptr %node, i64 48 |
| %v5 = load i64, ptr %d5, align 8 |
| call void @use(i64 %v5) |
| call void @free(ptr %node) |
| br label %done |
| |
| done: |
| ret void |
| } |
| |
| define void @test_empty_tree() { |
| ; CHECK-LABEL: define void @test_empty_tree() local_unnamed_addr { |
| ; CHECK-NEXT: tail call fastcc void @erase(ptr null) |
| ; CHECK-NEXT: ret void |
| ; |
| %tree = alloca ptr, align 8 |
| call void @ctor(ptr %tree) |
| call void @dtor(ptr %tree) |
| ret void |
| } |