GCC Named Return Value

Table of Contents

1. GCC Named Return Value

named return value (nrv) 是指返回 struct 的函数使用隐式传入的 retval 指令直接修改返回值, 而不是先修改局部变量然后在函数返回时再复制给 retval.

例如:

// 2022-04-19 11:57
struct X {
    int x;
    int y;
    int z;
    int a;
    int b;
};

struct X foo() {
    struct X ret;
    ret.x = 0xff;
    return ret;
}

不使用 nrv:

$> /opt/riscv/bin/riscv64-unknown-linux-gnu-gcc $PWD/test.c -O0 -c -fdump-tree-optimized
$> /opt/riscv/bin/riscv64-unknown-linux-gnu-objdump -d ./test.o

0000000000000000 <foo>:
   0:   7139                    addi    sp,sp,-64
   2:   fc22                    sd      s0,56(sp)
   4:   0080                    addi    s0,sp,64

   # a0 是返回址的指针
   6:   fca43423                sd      a0,-56(s0)

   # ret 的数据在栈上

   a:   0ff00793                li      a5,255
   e:   fcf42c23                sw      a5,-40(s0)
  12:   fc843783                ld      a5,-56(s0)
  16:   fd842703                lw      a4,-40(s0)
  1a:   853a                    mv      a0,a4
  1c:   fdc42703                lw      a4,-36(s0)
  20:   85ba                    mv      a1,a4
  22:   fe042703                lw      a4,-32(s0)
  26:   863a                    mv      a2,a4
  28:   fe442703                lw      a4,-28(s0)
  2c:   86ba                    mv      a3,a4
  2e:   fe842703                lw      a4,-24(s0)

   # 复制栈上的 ret 对 a0 指向的 retval

  32:   c388                    sw      a0,0(a5)
  34:   c3cc                    sw      a1,4(a5)
  36:   c790                    sw      a2,8(a5)
  38:   c7d4                    sw      a3,12(a5)
  3a:   cb98                    sw      a4,16(a5)

  3c:   fc843503                ld      a0,-56(s0)
  40:   7462                    ld      s0,56(sp)
  42:   6121                    addi    sp,sp,64
  44:   8082                    ret

$> cat test.c.244t.optimized

struct X foo ()
{
  struct X ret;

  <bb 2> :
  ret.x = 255;
  <retval> = ret;
  ret ={v} {CLOBBER};

  <bb 3> :
<L1>:
  return <retval>;

}

使用 nrv:

$> /opt/riscv/bin/riscv64-unknown-linux-gnu-gcc $PWD/test.c -O0 -c  -fenable-tree-nrv -fdump-tree-nrv
$> /opt/riscv/bin/riscv64-unknown-linux-gnu-objdump -d ./test.o

0000000000000000 <foo>:
   0:   1101                    addi    sp,sp,-32
   2:   ec22                    sd      s0,24(sp)
   4:   1000                    addi    s0,sp,32
   6:   fea43423                sd      a0,-24(s0)
   a:   fe843783                ld      a5,-24(s0)
   e:   0ff00713                li      a4,255

   # 把 0xff 直接写到了 a0 指向的 reval

  12:   c398                    sw      a4,0(a5)
  14:   0001                    nop
  16:   fe843503                ld      a0,-24(s0)
  1a:   6462                    ld      s0,24(sp)
  1c:   6105                    addi    sp,sp,32

$> cat test.c.242t.nrv

struct X foo ()
{
  struct X ret [value-expr: <retval>];

  <bb 2> :
  <retval>.x = 255;
  <retval> ={v} {CLOBBER};

  <bb 3> :
<L1>:
  return <retval>;

}

1.1. impls

unsigned int pass_nrv::execute(function *fun) {
    tree result = DECL_RESULT(current_function_decl);
    tree result_type = TREE_TYPE(result);
    tree found = NULL;
    basic_block bb;
    gimple_stmt_iterator gsi;
    struct nrv_data_t data;

    /* NOTE: 返回值需要是一个结构体 */
    if (!aggregate_value_p(result, current_function_decl)) return 0;

    if (is_gimple_reg_type(result_type)) return 0;

    if (TREE_ADDRESSABLE(result)) return 0;

    FOR_EACH_BB_FN(bb, fun) {
        for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
            gimple *stmt = gsi_stmt(gsi);

            if (gimple_has_lhs(stmt) && gimple_get_lhs(stmt) == result) {
                /* NOTE: <retval>=xxx 形式 */
                tree rhs;
                rhs = gimple_assign_rhs1(stmt);

                if (found != NULL) {
                    /* NOTE: 所有 `return xxx` 需要返回同一个变量 */
                    if (found != rhs) return 0;
                } else
                    found = rhs;

                /* NOTE: return 的变量不能是 volatile 或 addressable */
                if (!VAR_P(found) || TREE_THIS_VOLATILE(found) ||
                    !auto_var_in_fn_p(found, current_function_decl) ||
                    TREE_ADDRESSABLE(found) ||
                    !useless_type_conversion_p(result_type, TREE_TYPE(found)))
                    return 0;
            } else if (gimple_has_lhs(stmt)) {
                /* ... */
            }
        }
    }

    if (!found) return 0;

    /* Now walk through the function changing all references to VAR to be
       RESULT.  */
    data.var = found;
    data.result = result;
    FOR_EACH_BB_FN(bb, fun) {
        for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi);) {
            gimple *stmt = gsi_stmt(gsi);
            /* NOTE: <retval>=ret 形式的直接删除 */
            if (gimple_assign_copy_p(stmt) &&
                gimple_assign_lhs(stmt) == result &&
                gimple_assign_rhs1(stmt) == found) {
                unlink_stmt_vdef(stmt);
                gsi_remove(&gsi, true);
                release_defs(stmt);
            } else {
                /* NOTE: ret.x=xxx 形式的修改为 <retval>.x=xxx */
                struct walk_stmt_info wi;
                memset(&wi, 0, sizeof(wi));
                wi.info = &data;
                data.modified = 0;
                walk_gimple_op(stmt, finalize_nrv_r, &wi);
                if (data.modified) update_stmt(stmt);
                gsi_next(&gsi);
            }
        }
    }
    return 0;
}

bool auto_var_in_fn_p(const_tree var, const_tree fn) {
    return (
        DECL_P(var) && DECL_CONTEXT(var) == fn &&
        (auto_var_p(var) || TREE_CODE(var) == LABEL_DECL));
}

bool auto_var_p(const_tree var) {
    return (
        (((VAR_P(var) && !DECL_EXTERNAL(var)) || TREE_CODE(var) == PARM_DECL) &&
         !TREE_STATIC(var)) ||
        TREE_CODE(var) == RESULT_DECL);
}

1.2. 无法应用 nvr 的场景

  1. 返回不同的变量

    struct X foo(int x) {
        if (x > 0) {
            struct X ret;
            ret.x = 0xff;
            return ret;
        } else {
            struct X ret;
            return ret;
        }
    }
    
  2. volatile

    struct X foo() {
        /* volatile 导致 nrv 无效 */
        volatile struct X ret;
        ret.x = 0xff;
        return ret;
    }
    
    
  3. addressable

    struct X foo() {
        struct X ret;
        ret.x = 0xff;
    
        /* px 会导致 nrv 无效 */
        struct X* px = &ret;
    
        return ret;
    }
    
  4. ret 不是 auto_var

    ret 不是 auto_var, 所以对 ret 的修改会有`副作用`, 因此不能省略

    /* ret 是 external */
    struct X ret;
    struct X foo() {
        ret.x = 0xff;
        return ret;
    }
    
    /* ret 是 static */
    struct X foo() {
        static struct X ret;
        ret.x = 0xff;
        return ret;
    }
    

Backlinks

GCC Pass (GCC Pass > tree pass > pass_nrv): pass_nrv

GCC Return Slot (GCC Return Slot): pass_return_slot (name 是 retslot) 与 pass_nrv 都实现在 tree-nrv.c 中, 它与 pass_nrv 有关.

GCC Target Hook (GCC Target Hook > misc > TARGET_STRUCT_VALUE_RTX): 用来设置 GCC Named Return Value 如何把 nrv 传递给 callee

Author: [email protected]
Date: 2022-04-19 Tue 17:50
Last updated: 2024-09-08 Sun 21:24

知识共享许可协议