Skip to content

Fix: retain callable chip buffers by registration#1295

Open
puddingfjz wants to merge 1 commit into
hw-native-sys:mainfrom
puddingfjz:fix/issue-1082-unify-callable-buffer
Open

Fix: retain callable chip buffers by registration#1295
puddingfjz wants to merge 1 commit into
hw-native-sys:mainfrom
puddingfjz:fix/issue-1082-unify-callable-buffer

Conversation

@puddingfjz

Copy link
Copy Markdown
Contributor

Keep one refcounted ChipCallable buffer lease per registered callable id.

Use the retained chip storage slice as the device orch SO.

This removes the second orch SO upload for registered callables.

Release the retained chip buffer on unregister and registration failure.

Fixes #1082

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b21e033a-66e7-4df3-8225-763e9ba57da6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

CallableArtifacts and upload_and_collect_child_addrs are extended to expose chip-callable buffer device address, hash, and size. Onboard and sim DeviceRunnerBase implementations replace a separate orch-SO dedup pool with refcounted lifetime management of the chip-callable buffer, threading chip_buffer_hash/chip_dev through registration and adding release_chip_callable_buffer. Callable-registration glue and runtime maker call sites are updated accordingly.

Changes

ChipCallable Buffer Unification

Layer / File(s) Summary
Chip buffer metadata contract
src/common/task_interface/prepare_callable_common.h
CallableArtifacts gains chip_buffer_hash, chip_buffer_dev, chip_buffer_size; upload_and_collect_child_addrs adds optional output pointers and uses ChipCallableLayout for header sizing.
Onboard DeviceRunnerBase refcounting
src/common/platform/onboard/host/device_runner_base.{h,cpp}
Adds refcount to ChipCallableBuffer, adds release_chip_callable_buffer, extends record_device_orch_callable/record_host_orch_callable with chip-buffer identity, removes OrchSoBuffer/orch_so_dedup_, derives dev_orch_so_addr from the chip buffer, and updates unregister/finalize cleanup.
Sim DeviceRunnerBase refcounting
src/common/platform/sim/host/device_runner_base.{h,cpp}
Mirrors onboard changes: refcount field, chip_buffer_hash on CallableState, new release method, extended registration signatures, and removal of the old dedup pool.
Registration wiring with RAII guards
src/common/platform/onboard/host/c_api_shared.cpp, src/common/platform/sim/host/c_api_shared.cpp
simpler_register_callable adds a chip_buffer_guard released on failure and dismissed on success, passing chip-buffer identity into orch-registration calls.
Runtime maker call sites and comments
src/a2a3/runtime/.../runtime_maker.cpp, src/a5/runtime/.../runtime_maker.cpp, src/a2a3/platform/onboard/host/device_runner.h, src/a5/platform/onboard/host/device_runner.h
register_callable_impl collects chip buffer device/hash/size via upload_and_collect_child_addrs; "Group D state" comments updated to drop orch_so_dedup_/OrchSoBuffer.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Poem

A buffer once doubled, now merged into one,
No more orch dedup copies under the sun.
Refcounts tick down till the last hop is done,
Then GM is freed and the cleanup is won. 🐇
Hop, patch, release — the chip buffer's spun!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The host-build-graph runtime_maker files were modified to collect chip-buffer metadata, even though the issue said to leave that path unchanged. Remove or revert the host-build-graph chip-buffer plumbing unless it is strictly required, and keep that path using the existing host-side dlopen flow.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: retaining callable chip buffers by registration.
Description check ✅ Passed The description is clearly related to the PR and matches the chip-buffer retention and release changes.
Linked Issues check ✅ Passed The changes implement chip-buffer refcounting, point device orch SO at in-buffer storage, and release buffers on unregister/failure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the device runner to manage orchestration shared objects (SOs) directly within the refcounted chip callable buffers, eliminating the separate orch_so_dedup_ cache. It introduces a release mechanism for these buffers and integrates RAII guards in the C API to prevent memory leaks on registration failures. The review feedback recommends adding validation checks to ensure callable->child_count() is non-negative before layout computation and memory operations to prevent potential signed-to-unsigned conversion overflows.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 96 to 97
if (callable == nullptr || upload_fn == nullptr || out == nullptr) return -1;
out->clear();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When using a signed integer for a count parameter (such as callable->child_count()), always validate that the count is non-negative (e.g., callable->child_count() >= 0) before performing memory operations or casting to size_t to prevent potential signed-to-unsigned conversion overflows or negative bounds bypasses.

Suggested change
if (callable == nullptr || upload_fn == nullptr || out == nullptr) return -1;
out->clear();
if (callable == nullptr || upload_fn == nullptr || out == nullptr) return -1;
if (callable->child_count() < 0) return -1;
out->clear();
References
  1. When using a signed integer for a count or size parameter in functions that perform bounds checks and memory operations, always validate that the count is non-negative to prevent bypassing bounds checks and causing out-of-bounds writes or signed-to-unsigned conversion overflows.

Comment on lines +629 to 631
if (callable == nullptr) {
return 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Validate that callable->child_count() is non-negative before proceeding with layout computation and memory allocation to prevent potential signed-to-unsigned conversion overflows or negative bounds bypasses.

    if (callable == nullptr) {
        return 0;
    }
    if (callable->child_count() < 0) {
        LOG_ERROR("upload_chip_callable_buffer: negative child_count=%d", callable->child_count());
        return 0;
    }
References
  1. When using a signed integer for a count or size parameter in functions that perform bounds checks and memory operations, always validate that the count is non-negative to prevent bypassing bounds checks and causing out-of-bounds writes or signed-to-unsigned conversion overflows.

Comment on lines +571 to 573
if (callable == nullptr) {
return 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Validate that callable->child_count() is non-negative before proceeding with layout computation and memory allocation to prevent potential signed-to-unsigned conversion overflows or negative bounds bypasses.

Suggested change
if (callable == nullptr) {
return 0;
}
if (callable == nullptr) {
return 0;
}
if (callable->child_count() < 0) {
LOG_ERROR("upload_chip_callable_buffer: negative child_count=%d", callable->child_count());
return 0;
}
References
  1. When using a signed integer for a count or size parameter in functions that perform bounds checks and memory operations, always validate that the count is non-negative to prevent bypassing bounds checks and causing out-of-bounds writes or signed-to-unsigned conversion overflows.

Keep one refcounted ChipCallable buffer lease per registered callable id.

Use the retained chip storage slice as the device orch SO.

This removes the second orch SO upload for registered callables.

Release the retained chip buffer on unregister and registration failure.

Fixes hw-native-sys#1082
@puddingfjz puddingfjz force-pushed the fix/issue-1082-unify-callable-buffer branch from cbc5945 to c8f12ef Compare July 14, 2026 09:01

@ChaoWao ChaoWao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mechanism is correct and faithfully implements #1082 (both the double-upload and the leak-until-finalize). Verified the layout assumption (ChipCallable::binary_data() == storage_, orch SO is the leading slice) and traced every register/unregister failure path — the refcount balance holds on all of them. Leaving 3 non-blocking comments inline.

CallableState state = std::move(it->second);
callables_.erase(it);
aicpu_seen_callable_ids_.erase(callable_id);
release_chip_callable_buffer(state.chip_buffer_hash);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should fix — no regression test for this lifetime/refcount fix.

Both bugs #1082 describes are silent ("Neither is a crash today"), and every existing test passes with or without the fix: the prepared_callable ST asserts only functional correctness and the monotonic aicpu_dlopen_count (never decremented on unregister), so it cannot observe a chip-buffer leak; test_chip_callable_upload_immutable.cpp is a buffer-immutability contract test unrelated to refcounting.

If a future edit drops this release_chip_callable_buffer call, or the chip_buffer_guard/dismiss()CallableState ownership handoff regresses, nothing goes red. Per .claude/rules/discipline.md §3 a bugfix should leave a barrier behind.

Cheapest lock-in: a cpput test on DeviceRunnerBase — register a callable, assert chip_callable_buffers_.size() == 1; unregister, assert 0; register identical bytes at two cids, assert the dedup refcount, unregister one → still present, unregister the other → freed. (Alternatively expose a live-buffer counter mirroring aicpu_dlopen_count and assert delta == 0 after register+unregister in the existing ST.)

out->clear();
if (callable->child_count() <= 0) return 0;

const ChipCallableLayout layout = compute_chip_callable_layout(callable);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider (minor). compute_chip_callable_layout(callable).content_hash is computed here, and upload_chip_callable_buffer recomputes the same layout/hash internally to key chip_callable_buffers_. It's a once-per-registration FNV pass over the whole buffer — negligible, and the two-computation split keeps the upload_fn pointer signature clean. Fine to leave; noting only for completeness.

}

CallableState state;
state.hash = hash;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider (informational). With orch_so_dedup_ gone, state.hash (the orch ELF build-id) is no longer used for any buffer lookup — its only remaining consumer is the callable_hash() getter (unchanged by this PR, still used elsewhere). Not dead code, but the field's role has narrowed to just that accessor; worth a note so a future reader doesn't assume it still drives dedup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Code Health] AICPU orch .so is double-uploaded (orch_so_dedup_ duplicates the ChipCallable buffer) and chip_callable_buffers_ leaks until finalize

2 participants