| ; 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 |
| } |