Skip to content

fix(go-backend): match librime C struct layouts in 64-bit processes - #1010

Open
asdf80730 wants to merge 2 commits into
EasyIME:masterfrom
asdf80730:pr/rime-abi-fix
Open

asdf80730 wants to merge 2 commits into
EasyIME:masterfrom
asdf80730:pr/rime-abi-fix

Conversation

@asdf80730

@asdf80730 asdf80730 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

The Go backend hand-mirrors librime's C structs (rime_api.h, 1.16.1) and
all four mirrors drifted. The offsets happen to coincide in 32-bit, so the
shipped 386 backend hides it; in any 64-bit process the engine reads and
writes the wrong bytes: RimeSetup consumes stack garbage past the Go
traits (the trailing min_log_level/log_dir/prebuilt_data_dir/
staging_dir are absent), which randomly skips schema compilation;
RimeComposition.preedit is a plain char*, not a length-delimited view,
so RimeContext.menu sits 8 bytes off and RimeGetContext returns true
with an all-zero menu while the engine does have candidates (space still
commits the first one); RimeMenu's counts are plain int, not size_t;
and RimeCandidate carries a trailing void* reserved whose omission
breaks the array stride past the first candidate.

  • Realign all four mirrors byte-for-byte with rime_api.h (1.16.1) and
    set data_size to the exact Go sizeof, dropping the stale - 4
    fudge (librime.go).
  • Drive the rime runtime tests with the genuine luna_pinyin schema and
    dictionary from the repo's own brise submodule — the same source the
    python side uses — patching the user dir to a luna-only schema_list;
    an uninitialised submodule skips the suite with the init command in the
    message.

Validation:

  • GOARCH=amd64 go test ./input_methods/rime passes every runtime test
    (TestRealRimeCanCommitText for nihao/xiexie/shurufa/ceshi, plus the
    control-shortcut, backspace, escape and punctuation tests) against a
    brise-assembled user directory built from scratch. Before the fix the
    same run reported num=0 candidates while space still committed "你好".
  • arm64 hosts: runtime tests skip (x64 dll), unit tests pass.
  • The shipped cross-build CGO_ENABLED=0 GOOS=windows GOARCH=386 go build ./... passes; the 386 layouts are unchanged by construction.

Fully generated by omp (Qwen3.8-Flash-Next) without human intervention.

The hand-written mirrors drifted from rime_api.h (1.16.1) and only the
fields that happen to sit at identical offsets in 32-bit and 64-bit
reads survived, which is why the shipped 386 backend hid them:

- rimeTraitsC omitted min_log_level, log_dir, prebuilt_data_dir and
  staging_dir, so RimeSetup read up to 40 bytes past the Go allocation;
  stack garbage in prebuilt_data_dir / staging_dir made deployments
  silently skip schema compilation.
- RimeComposition.preedit is a plain char*, not a length-delimited view;
  the extra size_t shifted the menu by 8 bytes, so RimeGetContext
  returned true with an all-zero menu while the engine did have
  candidates (space still committed the first one).
- RimeMenu's count fields are plain ints, not size_t; the earlier
  uintptr guess misaligned the same region from the other side.
- RimeCandidate carries a trailing void* reserved; omitting it broke
  the array stride when reading page 2 and beyond.

DataSize values are now the exact Go sizeof, which equals the C size
with these layouts, dropping the stale "- 4" fudge.
Self-authored mini dictionaries compiled but never produced a single
candidate, and chasing that phantom burned a full debugging cycle. The
python side's brise submodule (official rime/brise, already a repo
dependency) carries the genuine luna_pinyin schema and dictionary, so
assemble the shared data directory from it instead of inventing one:
default.yaml, essay.txt, symbols.yaml and opencc/ plus the two preset
files, with the user dir patched to a luna_pinyin-only schema_list so
the first RimeInit compiles just that schema (~20 s, cached afterwards).

The shared/user pair is process-global because librime itself is a
process-wide singleton; PIME_TEST_SHARED_DIR and PIME_TEST_RIME_USER_DIR
still override both for live-data runs, and an uninitialised submodule
skips the suite with the init command in the message.
asdf80730 added a commit to asdf80730/PIME that referenced this pull request Oct 27, 2026
check_pr_scope.sh was a no-op in three ways at once: it required a local
branch named `master` this repo never has (so it skipped), it skipped
non-`pr/*` names unless opted in (the drifted branch was `omp/*`), and it
compared against `origin/master` — the fork's integration master, which
carries the harness by design, so a branch cut from it passed. The rime
ABI branch shipped 28 harness commits into PR EasyIME#1010 before a human caught
it.

Resolve the product master by remote URL (EasyIME/PIME) instead of remote
name, run for every non-integration branch, and fail either when the
product master is not an ancestor or when the diff carries docs/, tests/,
AGENTS.md or GLOSSARY.md. Teach pr_bodies to reject the generic
Summary/Evidence/Merge Danger headings upstream-pr.md already forbids —
the /pr skill's scaffold is the shape an agent reaches for when it does
not read that doc.

Generated by omp (Qwen3.8-Flash-Next) without human creative authorship.
SPDX-License-Identifier: LGPL-2.1-or-later
Co-Authored-By: omp (Qwen3.8-Flash-Next) <omp@local>

This branch has not been deployed

No deployments
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.

1 participant