| ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 | |
| ; RUN: opt -S -passes="default<O3>" < %s | FileCheck %s | |
| ; 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) { | |
| %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( | |
| ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] { | |
| ; CHECK-NEXT: ret void | |
| ; | |
| %tree = alloca ptr, align 8 | |
| call void @ctor(ptr %tree) | |
| call void @dtor(ptr %tree) | |
| ret void | |
| } |