Skip to content

later inline机制 #48

Description

@lfeng14

可以。先看最核心的策略:C2 在 Parse 调用点时,决定“立即 inline、late inline,还是保留调用”。

1. Parse 阶段的 inline 决策

flowchart TD
    A["Parse 遇到 invoke"] --> B{"允许 inline?"}

    B -- "否" --> CALL["生成普通 Call 节点<br/>不 inline"]
    B -- "是" --> INTR{"能使用 Intrinsic?"}

    INTR -- "是,且不是需延迟的 Vector" --> NOWI["立即生成 Intrinsic IR"]
    INTR -- "Vector 需要延迟" --> LATE["生成 Call 节点<br/>加入 _late_inlines"]
    INTR -- "否" --> MH{"MethodHandle 调用?"}

    MH -- "是,目标/参数现在明确" --> NOWMH["立即 inline MH"]
    MH -- "是,但目标/参数还不明确" --> LATEMH["先生成 Call<br/>等待常量传播后再尝试"]
    MH -- "否" --> TARGET{"当前能确定唯一目标?"}

    TARGET -- "否:virtual/interface" --> PROFILE{"类型 Profile / CHA<br/>能否找到候选目标?"}
    PROFILE -- "能" --> POLICY
    PROFILE -- "不能" --> VLATE{"IncrementalInlineVirtual?"}
    VLATE -- "是" --> LATEV["生成 CallDynamicJava<br/>等待 IGVN 去虚化"]
    VLATE -- "否" --> VCALL["保留 virtual call"]

    TARGET -- "是:static/special/已去虚化" --> POLICY{"InlineTree::ok_to_inline?"}

    POLICY -- "不满足 inline 策略" --> CALL
    POLICY -- "满足" --> DELAY{"should_delay?"}

    DELAY -- "是" --> LATE
    DELAY -- "否" --> SPECIAL{"特殊延迟类型?"}

    SPECIAL -- "StringBuilder/StringBuffer" --> LATES["进入 string late 队列"]
    SPECIAL -- "Boxing valueOf" --> LATEB["进入 boxing late 队列"]
    SPECIAL -- "Vector rebox" --> LATEVR["进入 vector reboxing late 队列"]
    SPECIAL -- "都不是" --> NOW["立即 inline<br/>callee 在 Parse 中展开"]
Loading

对应的主要代码在 doCall.cpp:164

最关键的分支是:

if (should_delay) {
    return CallGenerator::for_late_inline(callee, cg);
} else if (should_delay_string_inlining(...)) {
    return CallGenerator::for_string_late_inline(callee, cg);
} else if (should_delay_boxing_inlining(...)) {
    return CallGenerator::for_boxing_late_inline(callee, cg);
} else if (should_delay_vector_reboxing_inlining(...)) {
    return CallGenerator::for_vector_reboxing_late_inline(callee, cg);
} else {
    return cg;  // 立即 inline
}

doCall.cpp:190

2. should_delay 什么时候为 true?

可以概括为:

flowchart TD
    S["计算 should_delay"] --> A{"AlwaysIncrementalInline?"}
    A -- "是" --> Y["late inline"]
    A -- "否" --> B{"StressIncrementalInlining<br/>随机命中?"}
    B -- "是" --> Y
    B -- "否" --> C{"Replay 指定 inline_late?"}
    C -- "是" --> Y
    C -- "否" --> D{"ForceInline 方法<br/>但已碰到规模/深度限制?"}
    D -- "是" --> Y
    D -- "否" --> N["不延迟<br/>继续立即 inline"]

    D -. "限制包括" .-> D1["DesiredMethodLimit"]
    D -.-> D2["NodeCountInliningCutoff"]
    D -.-> D3["最大 inline depth"]
Loading

这里有一个很重要的细节:

默认 IncrementalInline=true,并不代表所有调用都 late inline。

默认:

IncrementalInline       = true
AlwaysIncrementalInline = false

所以普通、规模合适、目标明确的方法仍然立即 inline。通用 late inline 主要用于:

  • 强制 inline,但当前图已经很大
  • MethodHandle
  • 后续才能去虚化的 virtual call
  • Vector intrinsic
  • 编译重放或压力测试

相关限制判断在 bytecodeInfo.cpp:361

3. 进入 late 队列后怎么处理?

flowchart TD
    P["Parse 完成<br/>late 调用目前仍是 Call 节点"] --> RU["PhaseRemoveUseless<br/>删除 Parse 产生的无用节点"]
    RU --> IGVN["第一次 IGVN<br/>常量传播、类型收窄、死代码删除"]
    IGVN --> Q{"_late_inlines 非空?"}

    Q -- "否" --> NEXT["进入后续 C2 优化"]
    Q -- "是" --> LIMIT{"live_nodes 超过<br/>LiveNodeCountInliningCutoff?"}

    LIMIT -- "是" --> LOOP["尝试 PhaseIdealLoop<br/>清理和缩小图"]
    LOOP --> AGAIN{"清理后仍超限?"}
    AGAIN -- "是" --> STOP["停止继续 inline<br/>避免编译图爆炸"]
    AGAIN -- "否" --> PICK

    LIMIT -- "否" --> PICK["取一个 late inline candidate"]
    PICK --> LIVE{"原 Call 仍然存活且可达?"}

    LIVE -- "否" --> DROP["丢弃 candidate<br/>不再展开 callee"]
    DROP --> Q

    LIVE -- "是" --> TYPE{"virtual/MH 的目标和类型<br/>现在足够明确?"}
    TYPE -- "否" --> KEEP["暂不 inline<br/>保留或只做 direct-call 化"]
    KEEP --> Q

    TYPE -- "是" --> INLINE["重建调用点 JVMState<br/>inline_cg()->generate()"]
    INLINE --> NESTED{"callee 中又出现<br/>late inline?"}
    NESTED -- "是" --> DFS["插入当前队列位置<br/>近似深度优先处理"]
    NESTED -- "否" --> CLEAN
    DFS --> CLEAN{"需要 cleanup?"}

    CLEAN -- "是" --> OPT["RemoveUseless + IGVN"]
    CLEAN -- "否" --> Q
    OPT --> Q

    STOP --> LATEEND["到优化后期<br/>Virtual/MH 还可尝试<br/>strength reduce 为 direct call"]
    LATEEND --> NEXT
Loading

通用 late inline 的入口是:

inline_incrementally(igvn);

它位于第一次 IGVN 后,见 compile.cpp:2230

真正展开 callee 的位置是:

JVMState* new_jvms = inline_cg()->generate(jvms);

callGenerator.cpp:696

4. 特殊 late inline 的执行顺序

不同 late 队列的目的不同,因此执行位置也不一样:

flowchart LR
    P["Parse 完成"] --> S["StringOpts"]
    S --> G["普通 incremental<br/>late inline"]
    G --> SI["剩余 String<br/>late inline"]
    SI --> BE["Boxing Elimination"]
    BE --> BI["Boxing valueOf<br/>late inline"]
    BI --> V["Vector Optimization"]
    V --> VR["Vector reboxing<br/>late inline"]
    VR --> O["后续优化 / CodeGen"]
Loading

其中:

  • String late inline:先让 PhaseStringOpts 识别完整的字符串拼接结构,再展开剩余调用。
  • Boxing late inline:先尝试消除 boxing;无法消除的 valueOf() 再展开。
  • Vector reboxing late inline:等 Vector 优化处理完逻辑 Vector box 后再展开。
  • Virtual/MH late inline:等待类型或常量信息明确。

5. 最简单的判断口诀

目标明确 + 图不大 + 没有特殊优化需求
    → 立即 inline

已经决定应该 inline,但当前图太大
    → late inline,等清理后再展开

String / Boxing / Vector
    → 等对应的高层优化完成后再 inline

Virtual / MethodHandle 目标暂时不明确
    → 保留 Call,等 IGVN 类型传播后重新判断

Call 在等待期间变成死代码
    → 直接删除,不再 inline

等待后图仍然太大
    → 放弃 inline,最多尝试去虚化成 direct call

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions