From 2db82d7f78c9c1fdc132f89391cdeb85106a0ddc Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 07:55:10 +0000 Subject: [PATCH 01/14] bench: add CJK chat-completion fixture for UTF-8 validation perf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors medium_resp.json byte-for-byte (60368 B) but replaces the content field with 15000 × "中 " repetitions. The repeated 3-byte BMP CJK character forces the AVX2 string validator off its ASCII fast path on every chunk, exposing the scalar fallback cost that the upcoming SIMD UTF-8 validator targets. --- benches/fixtures/medium_resp_cjk.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 benches/fixtures/medium_resp_cjk.json diff --git a/benches/fixtures/medium_resp_cjk.json b/benches/fixtures/medium_resp_cjk.json new file mode 100644 index 0000000..c093d31 --- /dev/null +++ b/benches/fixtures/medium_resp_cjk.json @@ -0,0 +1,21 @@ +{ + "id": "resp_2026051599999", + "object": "chat.completion", + "created": 1747353600, + "model": "gpt-4", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 " + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 250, + "completion_tokens": 1500, + "total_tokens": 1750 + } +} From ef94e001714ad300d281e77cc5e3d456ac74cc75 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 08:02:33 +0000 Subject: [PATCH 02/14] bench: add Rust criterion harness for parse path Measures Document::parse_with_options end-to-end across ASCII and CJK fixtures in both EAGER and LAZY mode (4 benches total). Throughput is reported in MB/s. The eager-vs-lazy delta per fixture is the value-level validation cost that future SIMD optimizations target; the ASCII benches serve as a regression guard. --- Cargo.toml | 7 +++++- benches/parse_eager.rs | 53 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 benches/parse_eager.rs diff --git a/Cargo.toml b/Cargo.toml index 7089b77..d3e5346 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,12 @@ rustc-hash = "2" once_cell = "1" [dev-dependencies] -proptest = "1" +proptest = "1" +criterion = { version = "0.5", features = ["html_reports"] } + +[[bench]] +name = "parse_eager" +harness = false [profile.release] opt-level = 3 diff --git a/benches/parse_eager.rs b/benches/parse_eager.rs new file mode 100644 index 0000000..76b1dd2 --- /dev/null +++ b/benches/parse_eager.rs @@ -0,0 +1,53 @@ +//! End-to-end parse benchmark across ASCII and CJK fixtures, in both +//! EAGER and LAZY mode. Used to measure the cost of value-level +//! validation (the EAGER-vs-LAZY gap) — which is what the upcoming +//! SIMD UTF-8 validator targets — and to guard against ASCII-path +//! regressions. + +use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; +use qjson::doc::Document; +use qjson::options::{ + Options, QJSON_DEFAULT_MAX_DEPTH, QJSON_MODE_EAGER, QJSON_MODE_LAZY, +}; +use std::fs; + +const ASCII_PATH: &str = "benches/fixtures/medium_resp.json"; +const CJK_PATH: &str = "benches/fixtures/medium_resp_cjk.json"; + +fn run(c: &mut Criterion) { + for (name, path) in &[("ascii", ASCII_PATH), ("cjk", CJK_PATH)] { + let buf = fs::read(path) + .unwrap_or_else(|e| panic!("read {}: {}", path, e)); + let mut group = c.benchmark_group(format!("parse/{}", name)); + group.throughput(Throughput::Bytes(buf.len() as u64)); + + let eager = Options { + mode: QJSON_MODE_EAGER, + max_depth: QJSON_DEFAULT_MAX_DEPTH, + }; + let lazy = Options { + mode: QJSON_MODE_LAZY, + max_depth: QJSON_DEFAULT_MAX_DEPTH, + }; + + group.bench_function("eager", |b| { + b.iter(|| { + let doc = Document::parse_with_options(black_box(&buf), &eager) + .expect("parse eager"); + black_box(doc); + }) + }); + group.bench_function("lazy", |b| { + b.iter(|| { + let doc = Document::parse_with_options(black_box(&buf), &lazy) + .expect("parse lazy"); + black_box(doc); + }) + }); + + group.finish(); + } +} + +criterion_group!(benches, run); +criterion_main!(benches); From 113ce875e8254937b6f8ea9aececdefc7a51a529 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 08:11:35 +0000 Subject: [PATCH 03/14] build: split make bench into composite + bench-rust + bench-lua make bench now runs both Rust criterion and Lua-vs-cjson, matching the composition pattern used by make test. make bench-rust is the inner-loop target for SIMD tuning; make bench-lua preserves the existing user-facing comparison harness behavior unchanged. --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 628ee82..6fbb2c3 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ else LUA_ENV := LD_LIBRARY_PATH=$(LIB_DIR) LUA_PATH='$(LUA_PATH)' LUA_CPATH='$(LUA_CPATH)' endif -.PHONY: help build test lint bench clean +.PHONY: help build test lint bench bench-rust bench-lua clean help: ## Show this help @# FS uses [^#]* (not .*) so a description containing `##` isn't truncated. @@ -34,7 +34,12 @@ test: build ## Run cargo tests + busted Lua tests lint: ## Run clippy with -D warnings cargo clippy --release --all-targets -- -D warnings -bench: build vendor/lua-cjson/cjson.so ## Run the OpenResty LuaJIT benchmark +bench: bench-rust bench-lua ## Run all benchmarks (Rust criterion + Lua vs cjson) + +bench-rust: build ## Rust criterion microbench (parse path, MB/s + statistical CI) + cargo bench --bench parse_eager + +bench-lua: build vendor/lua-cjson/cjson.so ## Lua bench: qjson vs cjson vs simdjson $(LUA_ENV) $(RESTY) benches/lua_bench.lua vendor/lua-cjson/cjson.so: | vendor/lua-cjson/Makefile From aa6ba3124d3ac12e03f94ed2b207d04e82cfcc4a Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 08:18:39 +0000 Subject: [PATCH 04/14] docs: update make bench references for split Rust/Lua targets make bench is now the composite suite; make bench-lua preserves the prior Lua-vs-cjson behavior, which is what the benchmarks page documents. make bench-rust is the new Rust criterion entry point. --- README.md | 6 ++++-- docs/benchmarks.md | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c8a1462..bd252e3 100644 --- a/README.md +++ b/README.md @@ -118,12 +118,14 @@ and `simdjson` retain more Lua heap because they materialize the table tree. See [`docs/benchmarks.md`](docs/benchmarks.md) for the full size ladder, memory numbers, an "encode round-trip" row (passthrough emit via -`memcpy`), exact environment, and the reproduction command. `make bench` +`memcpy`), exact environment, and the reproduction command. `make bench-lua` uses `lua-resty-simdjson` when `resty.simdjson` is available in the OpenResty environment; otherwise it skips the simdjson rows. ```sh -make bench # qjson vs cjson and lua-resty-simdjson +make bench # full suite: Rust criterion + Lua vs cjson/simdjson +make bench-lua # Lua bench only (the table above) +make bench-rust # Rust criterion only (internal SIMD tuning) ``` ## RFC 8259 conformance diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 471cef8..38f9bf7 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -61,17 +61,17 @@ parsing workloads with ~3-5% structural density. ## Reproducing -Run the full comparison with one command: +Run the Lua-vs-cjson comparison (the table on this page) with: ```sh -make bench +make bench-lua ``` This builds `qjson`, builds the vendored `lua-cjson` against OpenResty's LuaJIT, then invokes `benches/lua_bench.lua` through OpenResty's `resty` so -`lua-resty-simdjson` runs in its normal `ngx` environment. -If `resty.simdjson` is not available on `package.path` / `package.cpath`, the -harness prints a skip message and omits the simdjson rows. +`lua-resty-simdjson` runs in its normal `ngx` environment. `make bench` +runs this plus the Rust criterion microbenchmark used for internal +optimization tracking. Numbers below come from one such run. From 039a2b50df71e733afd1421c84785882af8bc6d1 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 08:21:53 +0000 Subject: [PATCH 05/14] ci: compile benches in rust job to prevent bit-rot cargo build --release --benches catches stale bench source on every PR without paying the cost (or accepting the non-determinism) of actually running benchmarks in CI. --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 028dfa2..b624548 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,9 @@ jobs: - name: Build (release) run: cargo build --release + - name: Build benches (compile only, do not run) + run: cargo build --release --benches + - name: Test (release) run: cargo test --release From 151d0ea35889ae8931dd2c83897b6c9011132aca Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 08:36:40 +0000 Subject: [PATCH 06/14] docs: refresh CLAUDE.md for split bench targets Reflects make bench composition + the bench-rust / bench-lua sub-targets, and lists the new medium_resp_cjk.json fixture under benches/. Caught by the final review on the bench-infrastructure PR. --- CLAUDE.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 01885c7..d893f9e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,9 @@ The `Makefile` is the canonical entry point; `make help` lists targets. make build # cargo build --release → target/release/libqjson.so make test # cargo test --release + busted Lua tests make lint # cargo clippy --release --all-targets -- -D warnings -make bench # OpenResty LuaJIT benchmark vs lua-cjson and simdjson +make bench # Rust criterion (parse_eager) + OpenResty LuaJIT bench vs lua-cjson/simdjson +make bench-rust # Rust criterion only (fast inner loop for SIMD tuning) +make bench-lua # Lua bench only (qjson vs lua-cjson and simdjson) ``` Under the hood / for narrower invocations: @@ -79,7 +81,7 @@ src/ lua/qjson.lua LuaJIT wrapper (ffi.cdef + Doc/Cursor metatables) include/qjson.h public C header tests/ Rust integration tests + tests/lua/ busted suite -benches/ lua_bench.lua vs lua-cjson/simdjson; fixtures/ has small_api.json + medium_resp.json +benches/ parse_eager.rs (criterion) + lua_bench.lua vs lua-cjson/simdjson; fixtures/ has small_api.json + medium_resp.json + medium_resp_cjk.json ``` The enum values in `src/error.rs` are duplicated in `include/qjson.h` and `lua/qjson.lua` (the latter only encodes the `T_*` type tags and `NOT_FOUND = 2`). Keep all three in sync when adding/renumbering codes. From c0aaedd39939c54beeead52cbbe0c15b7e4226f7 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 09:24:55 +0000 Subject: [PATCH 07/14] test: add scalar-vs-avx2 string validator cross-check proptest property: validate_span_scalar and validate_span_avx2 must return byte-identical Result for any byte sequence (2000 cases per CI run). Mirrors tests/scanner_crosscheck.rs pattern. Passes on current code (AVX2 falls back to scalar on non-ASCII, so trivially identical); will catch any divergence introduced by the upcoming SIMD UTF-8 validator rewrite. --- src/lib.rs | 5 ++++ src/validate/strings/avx2.rs | 3 +- src/validate/strings/mod.rs | 6 ++-- src/validate/strings/scalar.rs | 3 +- ...g_validate_crosscheck.proptest-regressions | 0 tests/string_validate_crosscheck.rs | 28 +++++++++++++++++++ 6 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 tests/string_validate_crosscheck.proptest-regressions create mode 100644 tests/string_validate_crosscheck.rs diff --git a/src/lib.rs b/src/lib.rs index f75bb53..179e5d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,4 +18,9 @@ pub mod __test_api { pub use crate::scan::avx2::Avx2Scanner; #[cfg(target_arch = "aarch64")] pub use crate::scan::neon::NeonScanner; + + // String validator backends for cross-backend property testing. + pub use crate::validate::strings::scalar::validate_span_scalar; + #[cfg(all(target_arch = "x86_64", feature = "avx2"))] + pub use crate::validate::strings::avx2::validate_span_avx2; } diff --git a/src/validate/strings/avx2.rs b/src/validate/strings/avx2.rs index 8391a93..4d85dcd 100644 --- a/src/validate/strings/avx2.rs +++ b/src/validate/strings/avx2.rs @@ -18,7 +18,8 @@ use core::arch::x86_64::*; use super::scalar::validate_span_scalar; /// Validate `span` using AVX2 to bulk-skip pure-ASCII 32-byte chunks. -pub(crate) fn validate_span_avx2(span: &[u8]) -> Result<(), qjson_err> { +#[doc(hidden)] +pub fn validate_span_avx2(span: &[u8]) -> Result<(), qjson_err> { // SAFETY: dispatcher has verified the AVX2 feature is present. unsafe { validate_span_avx2_impl(span) } } diff --git a/src/validate/strings/mod.rs b/src/validate/strings/mod.rs index 18c160b..cb41c73 100644 --- a/src/validate/strings/mod.rs +++ b/src/validate/strings/mod.rs @@ -11,11 +11,11 @@ //! All paths return identical error codes for any input; the SIMD layers //! only accelerate the "this chunk is pure printable ASCII" common case. -mod scalar; +pub(crate) mod scalar; #[cfg(all(target_arch = "x86_64", feature = "avx2"))] -mod avx2; +pub(crate) mod avx2; #[cfg(target_arch = "aarch64")] -mod neon; +pub(crate) mod neon; use crate::error::qjson_err; use once_cell::sync::OnceCell; diff --git a/src/validate/strings/scalar.rs b/src/validate/strings/scalar.rs index c821709..2c319d2 100644 --- a/src/validate/strings/scalar.rs +++ b/src/validate/strings/scalar.rs @@ -22,7 +22,8 @@ use crate::error::qjson_err; /// Validate `span` byte-by-byte. The caller passes the unescaped string /// interior (between the JSON `"…"` quotes) — `\` therefore introduces an /// RFC 8259 escape sequence, not a literal backslash byte. -pub(crate) fn validate_span_scalar(span: &[u8]) -> Result<(), qjson_err> { +#[doc(hidden)] +pub fn validate_span_scalar(span: &[u8]) -> Result<(), qjson_err> { let mut i: usize = 0; let n = span.len(); while i < n { diff --git a/tests/string_validate_crosscheck.proptest-regressions b/tests/string_validate_crosscheck.proptest-regressions new file mode 100644 index 0000000..e69de29 diff --git a/tests/string_validate_crosscheck.rs b/tests/string_validate_crosscheck.rs new file mode 100644 index 0000000..823f221 --- /dev/null +++ b/tests/string_validate_crosscheck.rs @@ -0,0 +1,28 @@ +#[cfg(all(target_arch = "x86_64", feature = "avx2"))] +use proptest::prelude::*; + +#[cfg(all(target_arch = "x86_64", feature = "avx2"))] +use qjson::__test_api::{validate_span_scalar, validate_span_avx2}; + +#[cfg(all(target_arch = "x86_64", feature = "avx2"))] +proptest! { + #![proptest_config(ProptestConfig::with_cases(2000))] + + /// AVX2 and scalar validators must return byte-identical Result values + /// for every byte sequence: same Ok/Err, same error code on Err. + #[test] + fn scalar_avx2_match(input in prop::collection::vec(any::(), 0..256)) { + if !std::is_x86_feature_detected!("avx2") { + return Ok(()); + } + let scalar = validate_span_scalar(&input); + let avx2 = validate_span_avx2(&input); + prop_assert_eq!(&scalar, &avx2, + "validators disagree on input {:02X?}: scalar={:?} avx2={:?}", + &input, scalar, avx2); + } +} + +#[cfg(not(all(target_arch = "x86_64", feature = "avx2")))] +#[test] +fn skip_string_validate() {} From 100ffc7908fc41041353f35a40fca163d39fb407 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 09:34:40 +0000 Subject: [PATCH 08/14] test: add bad-UTF-8 reject-path fixtures and tests Three point tests for the three UTF-8 error classes the validator must reject: truncated multi-byte sequence (0xC3 with no continuation), overlong encoding (0xC0 0x80 = U+0000 in 2 bytes), and UTF-16 surrogate (0xED 0xA0 0x80 = U+D800). Tests pass on the current scalar fallback; they guard against regressions in the upcoming SIMD validator rewrite. --- tests/bad_utf8_fixtures.rs | 36 ++++++++++++++++++++++++++ tests/fixtures/bad_utf8_overlong.json | 1 + tests/fixtures/bad_utf8_surrogate.json | 1 + tests/fixtures/bad_utf8_truncated.json | 1 + 4 files changed, 39 insertions(+) create mode 100644 tests/bad_utf8_fixtures.rs create mode 100644 tests/fixtures/bad_utf8_overlong.json create mode 100644 tests/fixtures/bad_utf8_surrogate.json create mode 100644 tests/fixtures/bad_utf8_truncated.json diff --git a/tests/bad_utf8_fixtures.rs b/tests/bad_utf8_fixtures.rs new file mode 100644 index 0000000..3b648e6 --- /dev/null +++ b/tests/bad_utf8_fixtures.rs @@ -0,0 +1,36 @@ +use qjson::doc::Document; +use qjson::error::qjson_err; +use qjson::options::{Options, QJSON_DEFAULT_MAX_DEPTH, QJSON_MODE_EAGER}; + +fn assert_rejects_utf8(path: &str) { + let buf = std::fs::read(path) + .unwrap_or_else(|e| panic!("read {}: {}", path, e)); + let opts = Options { + mode: QJSON_MODE_EAGER, + max_depth: QJSON_DEFAULT_MAX_DEPTH, + }; + let err = Document::parse_with_options(&buf, &opts) + .err() + .unwrap_or_else(|| panic!("{} parsed successfully but should be rejected", path)); + assert_eq!( + err, + qjson_err::QJSON_INVALID_UTF8, + "{} rejected with wrong error code: {:?}", + path, err + ); +} + +#[test] +fn truncated_utf8_rejected() { + assert_rejects_utf8("tests/fixtures/bad_utf8_truncated.json"); +} + +#[test] +fn overlong_utf8_rejected() { + assert_rejects_utf8("tests/fixtures/bad_utf8_overlong.json"); +} + +#[test] +fn surrogate_utf8_rejected() { + assert_rejects_utf8("tests/fixtures/bad_utf8_surrogate.json"); +} diff --git a/tests/fixtures/bad_utf8_overlong.json b/tests/fixtures/bad_utf8_overlong.json new file mode 100644 index 0000000..5a19740 --- /dev/null +++ b/tests/fixtures/bad_utf8_overlong.json @@ -0,0 +1 @@ +{"key":"abcÀ€"} \ No newline at end of file diff --git a/tests/fixtures/bad_utf8_surrogate.json b/tests/fixtures/bad_utf8_surrogate.json new file mode 100644 index 0000000..f941029 --- /dev/null +++ b/tests/fixtures/bad_utf8_surrogate.json @@ -0,0 +1 @@ +{"key":"abcí €"} \ No newline at end of file diff --git a/tests/fixtures/bad_utf8_truncated.json b/tests/fixtures/bad_utf8_truncated.json new file mode 100644 index 0000000..f5231aa --- /dev/null +++ b/tests/fixtures/bad_utf8_truncated.json @@ -0,0 +1 @@ +{"key":"abcÃ"} \ No newline at end of file From d79e13198c836085b26e7a7f2f738ccdd97f765d Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 09:39:27 +0000 Subject: [PATCH 09/14] bench: add mixed-script and emoji-heavy bench fixtures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit medium_resp_mixed.json: 60368 B, content cycles "中 hello é world 😀 " (24 B/cycle × 2500 = 60000 B), exercising 1/2/3/4-byte UTF-8 sequences with frequent script transitions. medium_resp_emoji.json: 60368 B, content is "😀 " × 12000 (5 B/cycle), exercising the 4-byte UTF-8 lookup4 path under maximum pressure (80% high-bit ratio). Same skeleton and total byte count as medium_resp{,_cjk}.json so the bench's MB/s numbers are directly comparable across all four fixtures. --- benches/fixtures/medium_resp_emoji.json | 21 +++++++++++++++++++++ benches/fixtures/medium_resp_mixed.json | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 benches/fixtures/medium_resp_emoji.json create mode 100644 benches/fixtures/medium_resp_mixed.json diff --git a/benches/fixtures/medium_resp_emoji.json b/benches/fixtures/medium_resp_emoji.json new file mode 100644 index 0000000..5c939ea --- /dev/null +++ b/benches/fixtures/medium_resp_emoji.json @@ -0,0 +1,21 @@ +{ + "id": "resp_2026051599999", + "object": "chat.completion", + "created": 1747353600, + "model": "gpt-4", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 😀 " + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 250, + "completion_tokens": 1500, + "total_tokens": 1750 + } +} diff --git a/benches/fixtures/medium_resp_mixed.json b/benches/fixtures/medium_resp_mixed.json new file mode 100644 index 0000000..c19eda2 --- /dev/null +++ b/benches/fixtures/medium_resp_mixed.json @@ -0,0 +1,21 @@ +{ + "id": "resp_2026051599999", + "object": "chat.completion", + "created": 1747353600, + "model": "gpt-4", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 中 hello é world 😀 " + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 250, + "completion_tokens": 1500, + "total_tokens": 1750 + } +} From f7f07afad225dabebae2ecde8b88be203daf9e40 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 09:50:18 +0000 Subject: [PATCH 10/14] bench: extend matrix to 4 fixtures (ascii/cjk/mixed/emoji) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds parse/mixed/{eager,lazy} and parse/emoji/{eager,lazy} entries. Mixed exercises script-transition cost in the validator; emoji exercises 4-byte UTF-8 pressure. Lazy benches confirm scanner path is content-agnostic (all four should land within ±3% of each other). Numbers from this commit form the pre-simd baseline against which the upcoming AVX2 validator rewrite will be measured. --- benches/parse_eager.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/benches/parse_eager.rs b/benches/parse_eager.rs index 76b1dd2..8f29daa 100644 --- a/benches/parse_eager.rs +++ b/benches/parse_eager.rs @@ -11,11 +11,15 @@ use qjson::options::{ }; use std::fs; -const ASCII_PATH: &str = "benches/fixtures/medium_resp.json"; -const CJK_PATH: &str = "benches/fixtures/medium_resp_cjk.json"; +const FIXTURES: &[(&str, &str)] = &[ + ("ascii", "benches/fixtures/medium_resp.json"), + ("cjk", "benches/fixtures/medium_resp_cjk.json"), + ("mixed", "benches/fixtures/medium_resp_mixed.json"), + ("emoji", "benches/fixtures/medium_resp_emoji.json"), +]; fn run(c: &mut Criterion) { - for (name, path) in &[("ascii", ASCII_PATH), ("cjk", CJK_PATH)] { + for (name, path) in FIXTURES { let buf = fs::read(path) .unwrap_or_else(|e| panic!("read {}: {}", path, e)); let mut group = c.benchmark_group(format!("parse/{}", name)); From 268d3d2ebce67e0d3d666658e63d899d7a69c4c6 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 10:10:23 +0000 Subject: [PATCH 11/14] perf(validate): two-tier AVX2 UTF-8 validator with lookup4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the AVX2 fast-path-and-fallback layer with a three-tier dispatch: Tier 1: pure printable ASCII chunks skip wholesale (unchanged). Tier 2: pure UTF-8 chunks (no control/backslash) run Lemire/Keiser lookup4 — 5 SIMD ops/chunk, no scalar fallback. Tier 3: chunks with control byte or backslash flush the lookup4 carry, then hand off to the scalar state machine. Carry state across chunks: prev_input (256-bit) feeds shift-with-carry into lookup4's prev1/prev2/prev3 inputs. err_acc accumulates per-chunk errors; checked at chunk boundary before Tier 3 handoff and at end of main loop. prev_ended_ascii flag is the safety interlock for Tier 1. Lookup tables transcribed verbatim from simdjson's utf8_lookup4_algorithm.h (Lemire & Keiser, 2020). Correctness verified by tests/string_validate_crosscheck.rs (2000 proptest cases vs scalar oracle) and three explicit bad-UTF-8 reject fixtures. --- src/validate/strings/avx2.rs | 310 ++++++++++++++++++++++++++++++----- src/validate/strings/mod.rs | 26 +++ 2 files changed, 298 insertions(+), 38 deletions(-) diff --git a/src/validate/strings/avx2.rs b/src/validate/strings/avx2.rs index 4d85dcd..1ccc5f8 100644 --- a/src/validate/strings/avx2.rs +++ b/src/validate/strings/avx2.rs @@ -1,23 +1,187 @@ #![cfg(all(target_arch = "x86_64", feature = "avx2"))] -//! AVX2 ASCII fast path for string-content validation. +//! AVX2 three-tier string-content validator. //! -//! For each 32-byte chunk, compute a "needs-attention" mask covering bytes -//! that are either control chars (< 0x20), backslashes, or high-bit bytes. -//! If the mask is all-zero the chunk is pure printable ASCII (no escapes, -//! no UTF-8, no control) and can be skipped entirely. +//! Dispatch: +//! Tier 1 – pure printable ASCII chunk (high | ctrl | bs == 0, prev ended ASCII-safe): +//! skip wholesale, no SIMD validation needed. +//! Tier 2 – pure UTF-8 chunk (high != 0, ctrl | bs == 0): +//! run lookup4 AVX2 validator; continue SIMD loop. +//! Tier 3 – control byte or backslash present: +//! finalize lookup4 carry, then hand off to scalar state machine +//! from the first interesting byte. //! -//! On the first non-zero chunk we hand off to the scalar state machine for -//! the remainder of the span — we don't try to bit-scan inside the chunk. -//! The fast-path payoff comes from cleanly skipping long ASCII prefixes; -//! the scalar tail handles correctness without needing SIMD escape logic. +//! Cross-chunk UTF-8 carry state: +//! `prev_input` (256-bit) is the last processed chunk. It is shifted by 1, 2, +//! and 3 byte positions into `prev1`, `prev2`, `prev3` inside `lookup4_chunk`. +//! `err_acc` ORs per-chunk errors; checked at Tier 3 handoff and end of loop. +//! `prev_ended_ascii` gates Tier 1: a chunk whose last byte has the high bit +//! set cannot be followed by a Tier 1 skip — the interlock forces Tier 2/3 +//! so the cross-chunk multi-byte carry is validated. +//! +//! The lookup4 algorithm is transcribed from simdjson's +//! utf8_lookup4_algorithm.h (Lemire & Keiser, 2020). use crate::error::qjson_err; use core::arch::x86_64::*; -use super::scalar::validate_span_scalar; +// ── lookup4 UTF-8 validator constants (from simdjson) ─────────────────────── +// +// Three 16-byte lookup tables encode UTF-8 byte-pair validity. Each byte +// of a chunk is classified by the table lookup; the result of ANDing +// byte_1_high & byte_1_low & byte_2_high yields the per-byte error flags +// for check_special_cases. Combined with the check_multibyte_lengths +// result via XOR, any non-zero byte ⇒ invalid UTF-8. +// +// DO NOT re-derive — values transcribed verbatim from simdjson source. + +const TOO_SHORT: u8 = 1 << 0; // lead byte followed by lead/ASCII +const TOO_LONG: u8 = 1 << 1; // ASCII followed by continuation +const OVERLONG_3: u8 = 1 << 2; // E0 80..9F +const SURROGATE: u8 = 1 << 4; // ED A0..BF +const OVERLONG_2: u8 = 1 << 5; // C0/C1 80..BF +const TWO_CONTS: u8 = 1 << 7; // 10______ 10______ +const TOO_LARGE: u8 = 1 << 3; // > U+10FFFF +const TOO_LARGE_1000: u8 = 1 << 6; +const OVERLONG_4: u8 = 1 << 6; // F0 80..8F +const CARRY: u8 = TOO_SHORT | TOO_LONG | TWO_CONTS; + +/// High nibble of prev1 (byte i−1): `byte_1_high` in simdjson terminology. +#[rustfmt::skip] +const BYTE1_HIGH: [u8; 16] = [ + // 0_______ = ASCII in byte 1 + TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, + TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, + // 10______ = continuation in byte 1 + TWO_CONTS, TWO_CONTS, TWO_CONTS, TWO_CONTS, + // 1100____ = 2-byte lead (incl. C0/C1 → OVERLONG_2) + TOO_SHORT | OVERLONG_2, + // 1101____ = 2-byte lead + TOO_SHORT, + // 1110____ = 3-byte lead (incl. E0 → OVERLONG_3, ED → SURROGATE) + TOO_SHORT | OVERLONG_3 | SURROGATE, + // 1111____ = 4-byte lead (incl. F0 → OVERLONG_4, F4+ → TOO_LARGE) + TOO_SHORT | TOO_LARGE | TOO_LARGE_1000 | OVERLONG_4, +]; + +/// Low nibble of prev1: `byte_1_low` in simdjson terminology. +#[rustfmt::skip] +const BYTE1_LOW: [u8; 16] = [ + // ____0000 + CARRY | OVERLONG_3 | OVERLONG_2 | OVERLONG_4, + // ____0001 + CARRY | OVERLONG_2, + // ____0010, ____0011 + CARRY, + CARRY, + // ____0100 + CARRY | TOO_LARGE, + // ____0101 + CARRY | TOO_LARGE | TOO_LARGE_1000, + // ____011_ + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + // ____1___ + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + // ____1101 + CARRY | TOO_LARGE | TOO_LARGE_1000 | SURROGATE, + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, +]; + +/// High nibble of current byte (byte i): `byte_2_high` in simdjson terminology. +/// +/// This is the lookup for the *current* byte (not the prev1 byte). +/// - ASCII bytes (nibbles 0-7): `TOO_SHORT` +/// - Continuation 0x80-0x8F (nibble 8): `TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE_1000 | OVERLONG_4` +/// - Continuation 0x90-0x9F (nibble 9): `TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE` +/// - Continuation 0xA0-0xBF (nibbles A-B): `TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE` +/// - Lead bytes 0xC0-0xFF (nibbles C-F): `TOO_SHORT` +#[rustfmt::skip] +const BYTE2_HIGH: [u8; 16] = [ + // ________ 0_______ = ASCII in byte 2 + TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, + TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, + // ________ 1000____ = continuation in byte 2 + TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE_1000 | OVERLONG_4, + // ________ 1001____ + TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE, + // ________ 101_____ + TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, + TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, + // ________ 11______ = lead byte in byte 2 + TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, +]; + +/// Compute the lookup4 error mask for a 32-byte chunk. +/// +/// Implements `check_special_cases` XOR `check_multibyte_lengths & 0x80` +/// from simdjson, adapted for AVX2 (256-bit = 2 × 128-bit lanes). +/// +/// `prev_input` is updated to the current chunk on return (carry for next call). +/// Returns a 256-bit mask: any non-zero byte ⇒ UTF-8 error at that position. +#[inline] +#[target_feature(enable = "avx2")] +unsafe fn lookup4_chunk(chunk: __m256i, prev_input: &mut __m256i) -> __m256i { + // ─── build prev1 / prev2 / prev3 ──────────────────────────────────────── + // `_mm256_permute2x128_si256::<0x21>(*prev_input, chunk)` splices the + // upper 128-bit lane of prev_input with the lower lane of chunk, producing + // a 256-bit intermediate that feeds `_mm256_alignr_epi8` (which operates + // per 128-bit lane). + let cross = _mm256_permute2x128_si256::<0x21>(*prev_input, chunk); + + // prev1[i] = chunk[i-1], with prev_input's last byte at position 0. + let prev1 = _mm256_alignr_epi8::<15>(chunk, cross); + // prev2[i] = chunk[i-2] + let prev2 = _mm256_alignr_epi8::<14>(chunk, cross); + // prev3[i] = chunk[i-3] + let prev3 = _mm256_alignr_epi8::<13>(chunk, cross); + + // ─── check_special_cases ──────────────────────────────────────────────── + let nibble_mask = _mm256_set1_epi8(0x0F); + + let hi1 = _mm256_and_si256(_mm256_srli_epi16(prev1, 4), nibble_mask); + let lo1 = _mm256_and_si256(prev1, nibble_mask); + let hi2 = _mm256_and_si256(_mm256_srli_epi16(chunk, 4), nibble_mask); + + let byte1_high_tbl = _mm256_broadcastsi128_si256( + _mm_loadu_si128(BYTE1_HIGH.as_ptr() as *const __m128i)); + let byte1_low_tbl = _mm256_broadcastsi128_si256( + _mm_loadu_si128(BYTE1_LOW.as_ptr() as *const __m128i)); + let byte2_high_tbl = _mm256_broadcastsi128_si256( + _mm_loadu_si128(BYTE2_HIGH.as_ptr() as *const __m128i)); + + let sc = _mm256_and_si256( + _mm256_and_si256( + _mm256_shuffle_epi8(byte1_high_tbl, hi1), + _mm256_shuffle_epi8(byte1_low_tbl, lo1), + ), + _mm256_shuffle_epi8(byte2_high_tbl, hi2), + ); + + // ─── check_multibyte_lengths ──────────────────────────────────────────── + // must_be_2_3_continuation(prev2, prev3): + // is_third_byte = saturating_sub(prev2, 0xE0 - 0x80) i.e. saturating_sub(prev2, 0x60) + // is_fourth_byte = saturating_sub(prev3, 0xF0 - 0x80) i.e. saturating_sub(prev3, 0x70) + // Only bytes >= 0xE0 / >= 0xF0 respectively produce non-zero results. + let is_third = _mm256_subs_epu8(prev2, _mm256_set1_epi8(0x60_u8 as i8)); + let is_fourth = _mm256_subs_epu8(prev3, _mm256_set1_epi8(0x70_u8 as i8)); + let must23 = _mm256_or_si256(is_third, is_fourth); + // & 0x80: only keep the high bit (any non-zero saturated value sets bit 7 after & 0x80) + let must23_80 = _mm256_and_si256(must23, _mm256_set1_epi8(0x80_u8 as i8)); -/// Validate `span` using AVX2 to bulk-skip pure-ASCII 32-byte chunks. + // Update carry for next chunk. + *prev_input = chunk; + + // Final error mask: XOR cancels out the expected bits. + _mm256_xor_si256(must23_80, sc) +} + +/// Validate `span` using AVX2 three-tier dispatch. #[doc(hidden)] pub fn validate_span_avx2(span: &[u8]) -> Result<(), qjson_err> { // SAFETY: dispatcher has verified the AVX2 feature is present. @@ -29,41 +193,111 @@ unsafe fn validate_span_avx2_impl(span: &[u8]) -> Result<(), qjson_err> { let mut i: usize = 0; let n = span.len(); - // ASCII bytes that need scalar attention have: - // - top bit set → byte >= 0x80 - // - value < 0x20 → control char - // - value == 0x5C ('\\') → escape introducer - // - // Detection via three SIMD compares OR'd together. - let backslash = _mm256_set1_epi8(b'\\' as i8); - // For "< 0x20" we use a signed unsigned trick: compare against 0x1F via - // unsigned MAX. _mm256_cmpgt_epi8 is signed, but bytes <0x20 are also - // <0x20 as signed positive values, so signed cmpgt works here for the - // 0x00..=0x1F range (none of which has the high bit set). - let ctrl_thresh = _mm256_set1_epi8(0x20_i8); + let mut prev_input = _mm256_setzero_si256(); + let mut err_acc = _mm256_setzero_si256(); + let mut prev_ended_ascii = true; while i + 32 <= n { let chunk = _mm256_loadu_si256(span.as_ptr().add(i) as *const __m256i); - // high bit set? let high = _mm256_movemask_epi8(chunk) as u32; - // byte == '\\' ? - let bs = _mm256_movemask_epi8(_mm256_cmpeq_epi8(chunk, backslash)) as u32; - // byte < 0x20 ? (signed cmpgt: ctrl_thresh > chunk for 0x00..=0x1F bytes) - let ctrl = _mm256_movemask_epi8(_mm256_cmpgt_epi8(ctrl_thresh, chunk)) as u32; - - let interesting = high | bs | ctrl; - if interesting != 0 { - // Hand off to the scalar state machine starting at the first - // interesting byte in this chunk. We don't try to validate any - // already-cleared bytes — those are pure printable ASCII and - // self-terminating so it's safe to resume there. - let offset = interesting.trailing_zeros() as usize; - return validate_span_scalar(&span[i + offset..]); + let ctrl = _mm256_movemask_epi8(_mm256_cmpgt_epi8( + _mm256_set1_epi8(0x20), + chunk, + )) as u32; + let bs = _mm256_movemask_epi8(_mm256_cmpeq_epi8( + chunk, + _mm256_set1_epi8(b'\\' as i8), + )) as u32; + + // Tier 1: pure printable ASCII, and previous chunk also ended ASCII-safe. + if (high | ctrl | bs) == 0 && prev_ended_ascii { + prev_input = chunk; + // prev_ended_ascii stays true (all bytes < 0x80) + i += 32; + continue; + } + + // Tier 3: control byte or backslash → do NOT run lookup4 on this + // chunk (the scalar path handles it correctly). First flush any UTF-8 + // errors accumulated from prior Tier 2 chunks, then hand off to + // scalar starting at the appropriate byte. + if (ctrl | bs) != 0 { + if _mm256_testz_si256(err_acc, err_acc) == 0 { + return Err(qjson_err::QJSON_INVALID_UTF8); + } + // If the previous chunk ended with a multi-byte lead, we need to + // include those unfinished bytes in the scalar input so it can + // validate the full sequence (including the continuation/error). + if !prev_ended_ascii { + let last3 = [ + _mm256_extract_epi8::<29>(prev_input) as u8, + _mm256_extract_epi8::<30>(prev_input) as u8, + _mm256_extract_epi8::<31>(prev_input) as u8, + ]; + let mut prefix_len = 0usize; + if (0xC2..=0xF4).contains(&last3[2]) { + prefix_len = 1; + } else if (0xC2..=0xF4).contains(&last3[1]) { + prefix_len = 2; + } else if (0xC2..=0xF4).contains(&last3[0]) { + prefix_len = 3; + } + if prefix_len > 0 { + let mut combined = Vec::with_capacity(prefix_len + (n - i)); + combined.extend_from_slice(&last3[3 - prefix_len..]); + combined.extend_from_slice(&span[i..]); + return super::scalar::validate_span_scalar(&combined); + } + } + // If there are no high-bit bytes in this chunk, bytes before the + // first ctrl/bs are pure ASCII and need no validation — skip them. + // If there ARE high-bit bytes, scalar must start from the beginning + // of the chunk to correctly validate UTF-8 sequences. + let start = if high == 0 { + i + (ctrl | bs).trailing_zeros() as usize + } else { + i + }; + return super::scalar::validate_span_scalar(&span[start..]); } + // Tier 2: pure UTF-8 (no control, no backslash) → run lookup4. + err_acc = _mm256_or_si256(err_acc, lookup4_chunk(chunk, &mut prev_input)); + prev_ended_ascii = (span[i + 31] & 0x80) == 0; i += 32; } - validate_span_scalar(&span[i..]) + // After main loop: if err_acc accumulated any UTF-8 errors, fail before tail. + if _mm256_testz_si256(err_acc, err_acc) == 0 { + return Err(qjson_err::QJSON_INVALID_UTF8); + } + + // Standard tail: if prev_ended_ascii, scalar starts fresh at i. + // If !prev_ended_ascii, the previous chunk may have left an unfinished + // multi-byte lead; reconstruct a buffer containing those bytes plus the + // tail and validate as a unit. + if !prev_ended_ascii { + let last3 = [ + _mm256_extract_epi8::<29>(prev_input) as u8, + _mm256_extract_epi8::<30>(prev_input) as u8, + _mm256_extract_epi8::<31>(prev_input) as u8, + ]; + let mut prefix_len = 0usize; + if (0xC2..=0xF4).contains(&last3[2]) { + prefix_len = 1; + } else if (0xC2..=0xF4).contains(&last3[1]) { + prefix_len = 2; + } else if (0xC2..=0xF4).contains(&last3[0]) { + prefix_len = 3; + } + if prefix_len > 0 { + let mut combined = Vec::with_capacity(prefix_len + (n - i)); + combined.extend_from_slice(&last3[3 - prefix_len..]); + combined.extend_from_slice(&span[i..]); + return super::scalar::validate_span_scalar(&combined); + } + } + + super::scalar::validate_span_scalar(&span[i..]) } diff --git a/src/validate/strings/mod.rs b/src/validate/strings/mod.rs index cb41c73..59a2699 100644 --- a/src/validate/strings/mod.rs +++ b/src/validate/strings/mod.rs @@ -189,4 +189,30 @@ mod tests { assert_eq!(validate_string_span(&[0xF5, 0x80, 0x80, 0x80]).unwrap_err(), qjson_err::QJSON_INVALID_UTF8); assert_eq!(validate_string_span(&[0xFF]).unwrap_err(), qjson_err::QJSON_INVALID_UTF8); } + + #[test] + fn three_byte_lead_at_chunk_boundary_then_ascii_chunk() { + // Reproduces the Tier 1 / Tier 2 / Tier 3 safety-interlock case + // documented in the spec's Risks section. + // Chunk 0: 31 ASCII bytes + 0xE4 (3-byte CJK lead). + // Chunk 1: 0xB8 0xAD (valid continuations) + ASCII content... + let mut s = vec![b'x'; 31]; + s.push(0xE4); + s.extend_from_slice(&[0xB8, 0xAD]); // completes 中 + s.extend_from_slice(b" rest of content"); + assert!(validate_string_span(&s).is_ok()); + } + + #[test] + fn three_byte_lead_at_chunk_end_followed_by_pure_ascii_chunk() { + // Chunk 0 ends with 0xE4 (lead expecting 2 continuations). + // Chunk 1 is pure ASCII (no continuation). MUST reject as INVALID_UTF8. + let mut s = vec![b'x'; 31]; + s.push(0xE4); // 32 bytes total, last byte is unfinished lead + s.extend_from_slice(&[b'A'; 32]); // pure ASCII chunk follows + assert_eq!( + validate_string_span(&s).unwrap_err(), + qjson_err::QJSON_INVALID_UTF8 + ); + } } From f8269013f3f47c45758219824ed28cc4ca3244c7 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 10:17:18 +0000 Subject: [PATCH 12/14] test: pin regression seed found during lookup4 implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit proptest captured the failing seed [0x00 × 30, 0x80, 0x00] that surfaced during the AVX2 lookup4 development — initial transcription of simdjson's tables had errors that this input revealed within seconds of running the cross-check. Committing the seed ensures the exact bug-pattern is replayed on every future run. --- tests/string_validate_crosscheck.proptest-regressions | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/string_validate_crosscheck.proptest-regressions b/tests/string_validate_crosscheck.proptest-regressions index e69de29..6b7c496 100644 --- a/tests/string_validate_crosscheck.proptest-regressions +++ b/tests/string_validate_crosscheck.proptest-regressions @@ -0,0 +1 @@ +cc 24bf71ed2e99152e4fd3e67450bfcfffe011e74c788cc530cc59f8a3ce56a3fc # shrinks to input = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0] From 47b8fb51eb56d7eb8de82262a622c40a08cd6fd2 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 10:24:07 +0000 Subject: [PATCH 13/14] refactor(validate): hoist lookup4 broadcasts + dedup carry extraction Three code-quality fixes from review of the AVX2 lookup4 commit: - lookup4_chunk no longer re-broadcasts BYTE1_HIGH / BYTE1_LOW / BYTE2_HIGH on every call; the three table vectors are computed once at the top of validate_span_avx2_impl and passed in. - prev_ended_ascii's safety role at the Tier 1 skip is now documented inline (was only in the module-level doc). - The 1-3 byte carry-prefix extraction from prev_input was duplicated in the Tier 3 fallback and the post-loop tail; extracted into a private extract_carry_prefix helper so both sites share one source of truth. No behavior change; cross-check property test still passes 2000 cases. --- src/validate/strings/avx2.rs | 101 +++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/src/validate/strings/avx2.rs b/src/validate/strings/avx2.rs index 1ccc5f8..3534da9 100644 --- a/src/validate/strings/avx2.rs +++ b/src/validate/strings/avx2.rs @@ -117,16 +117,52 @@ const BYTE2_HIGH: [u8; 16] = [ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ]; +/// Returns the 1-3 byte carry prefix from `prev_input`'s tail, or `None` +/// if the prior chunk ended on a complete sequence. +/// +/// The range `0xC2..=0xF4` covers all valid UTF-8 lead bytes: 0xC2 is the +/// first non-overlong 2-byte lead; 0xF4 is the last valid 4-byte lead. +/// Any byte outside this range cannot start a multi-byte sequence whose +/// continuation crosses the chunk boundary. +#[inline] +#[target_feature(enable = "avx2")] +unsafe fn extract_carry_prefix(prev_input: __m256i) -> Option> { + let last3 = [ + _mm256_extract_epi8::<29>(prev_input) as u8, + _mm256_extract_epi8::<30>(prev_input) as u8, + _mm256_extract_epi8::<31>(prev_input) as u8, + ]; + let prefix_len = if (0xC2..=0xF4).contains(&last3[2]) { + 1 + } else if (0xC2..=0xF4).contains(&last3[1]) { + 2 + } else if (0xC2..=0xF4).contains(&last3[0]) { + 3 + } else { + return None; + }; + Some(last3[3 - prefix_len..].to_vec()) +} + /// Compute the lookup4 error mask for a 32-byte chunk. /// /// Implements `check_special_cases` XOR `check_multibyte_lengths & 0x80` /// from simdjson, adapted for AVX2 (256-bit = 2 × 128-bit lanes). /// /// `prev_input` is updated to the current chunk on return (carry for next call). +/// The three broadcast table vectors (`byte1_high_tbl`, `byte1_low_tbl`, +/// `byte2_high_tbl`) are computed once by the caller and passed in to avoid +/// re-broadcasting on every call. /// Returns a 256-bit mask: any non-zero byte ⇒ UTF-8 error at that position. #[inline] #[target_feature(enable = "avx2")] -unsafe fn lookup4_chunk(chunk: __m256i, prev_input: &mut __m256i) -> __m256i { +unsafe fn lookup4_chunk( + chunk: __m256i, + prev_input: &mut __m256i, + byte1_high_tbl: __m256i, + byte1_low_tbl: __m256i, + byte2_high_tbl: __m256i, +) -> __m256i { // ─── build prev1 / prev2 / prev3 ──────────────────────────────────────── // `_mm256_permute2x128_si256::<0x21>(*prev_input, chunk)` splices the // upper 128-bit lane of prev_input with the lower lane of chunk, producing @@ -148,13 +184,6 @@ unsafe fn lookup4_chunk(chunk: __m256i, prev_input: &mut __m256i) -> __m256i { let lo1 = _mm256_and_si256(prev1, nibble_mask); let hi2 = _mm256_and_si256(_mm256_srli_epi16(chunk, 4), nibble_mask); - let byte1_high_tbl = _mm256_broadcastsi128_si256( - _mm_loadu_si128(BYTE1_HIGH.as_ptr() as *const __m128i)); - let byte1_low_tbl = _mm256_broadcastsi128_si256( - _mm_loadu_si128(BYTE1_LOW.as_ptr() as *const __m128i)); - let byte2_high_tbl = _mm256_broadcastsi128_si256( - _mm_loadu_si128(BYTE2_HIGH.as_ptr() as *const __m128i)); - let sc = _mm256_and_si256( _mm256_and_si256( _mm256_shuffle_epi8(byte1_high_tbl, hi1), @@ -197,6 +226,15 @@ unsafe fn validate_span_avx2_impl(span: &[u8]) -> Result<(), qjson_err> { let mut err_acc = _mm256_setzero_si256(); let mut prev_ended_ascii = true; + // Hoist the three lookup4 broadcast table vectors out of the hot loop so + // they are computed once and reused across every lookup4_chunk call. + let byte1_high_tbl = _mm256_broadcastsi128_si256( + _mm_loadu_si128(BYTE1_HIGH.as_ptr() as *const __m128i)); + let byte1_low_tbl = _mm256_broadcastsi128_si256( + _mm_loadu_si128(BYTE1_LOW.as_ptr() as *const __m128i)); + let byte2_high_tbl = _mm256_broadcastsi128_si256( + _mm_loadu_si128(BYTE2_HIGH.as_ptr() as *const __m128i)); + while i + 32 <= n { let chunk = _mm256_loadu_si256(span.as_ptr().add(i) as *const __m256i); @@ -210,6 +248,10 @@ unsafe fn validate_span_avx2_impl(span: &[u8]) -> Result<(), qjson_err> { _mm256_set1_epi8(b'\\' as i8), )) as u32; + // prev_ended_ascii: last byte of prior chunk had high bit clear, so no + // cross-chunk multi-byte carry is pending. Without this guard, a lead + // byte ending chunk N could cause lookup4 to miss its continuations in + // chunk N+1. // Tier 1: pure printable ASCII, and previous chunk also ended ASCII-safe. if (high | ctrl | bs) == 0 && prev_ended_ascii { prev_input = chunk; @@ -230,22 +272,9 @@ unsafe fn validate_span_avx2_impl(span: &[u8]) -> Result<(), qjson_err> { // include those unfinished bytes in the scalar input so it can // validate the full sequence (including the continuation/error). if !prev_ended_ascii { - let last3 = [ - _mm256_extract_epi8::<29>(prev_input) as u8, - _mm256_extract_epi8::<30>(prev_input) as u8, - _mm256_extract_epi8::<31>(prev_input) as u8, - ]; - let mut prefix_len = 0usize; - if (0xC2..=0xF4).contains(&last3[2]) { - prefix_len = 1; - } else if (0xC2..=0xF4).contains(&last3[1]) { - prefix_len = 2; - } else if (0xC2..=0xF4).contains(&last3[0]) { - prefix_len = 3; - } - if prefix_len > 0 { - let mut combined = Vec::with_capacity(prefix_len + (n - i)); - combined.extend_from_slice(&last3[3 - prefix_len..]); + if let Some(carry) = extract_carry_prefix(prev_input) { + let mut combined = Vec::with_capacity(carry.len() + (n - i)); + combined.extend_from_slice(&carry); combined.extend_from_slice(&span[i..]); return super::scalar::validate_span_scalar(&combined); } @@ -263,7 +292,10 @@ unsafe fn validate_span_avx2_impl(span: &[u8]) -> Result<(), qjson_err> { } // Tier 2: pure UTF-8 (no control, no backslash) → run lookup4. - err_acc = _mm256_or_si256(err_acc, lookup4_chunk(chunk, &mut prev_input)); + err_acc = _mm256_or_si256( + err_acc, + lookup4_chunk(chunk, &mut prev_input, byte1_high_tbl, byte1_low_tbl, byte2_high_tbl), + ); prev_ended_ascii = (span[i + 31] & 0x80) == 0; i += 32; } @@ -278,22 +310,9 @@ unsafe fn validate_span_avx2_impl(span: &[u8]) -> Result<(), qjson_err> { // multi-byte lead; reconstruct a buffer containing those bytes plus the // tail and validate as a unit. if !prev_ended_ascii { - let last3 = [ - _mm256_extract_epi8::<29>(prev_input) as u8, - _mm256_extract_epi8::<30>(prev_input) as u8, - _mm256_extract_epi8::<31>(prev_input) as u8, - ]; - let mut prefix_len = 0usize; - if (0xC2..=0xF4).contains(&last3[2]) { - prefix_len = 1; - } else if (0xC2..=0xF4).contains(&last3[1]) { - prefix_len = 2; - } else if (0xC2..=0xF4).contains(&last3[0]) { - prefix_len = 3; - } - if prefix_len > 0 { - let mut combined = Vec::with_capacity(prefix_len + (n - i)); - combined.extend_from_slice(&last3[3 - prefix_len..]); + if let Some(carry) = extract_carry_prefix(prev_input) { + let mut combined = Vec::with_capacity(carry.len() + (n - i)); + combined.extend_from_slice(&carry); combined.extend_from_slice(&span[i..]); return super::scalar::validate_span_scalar(&combined); } From 7e77b51f81e43eacf7e7c8865b686c23b4558626 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 22 May 2026 11:03:37 +0000 Subject: [PATCH 14/14] perf(validate): recover ASCII fast-path with fused vpor+vpmovmskb The three-tier dispatch in 47b8fb5 broke the compiler's ability to collapse the high/ctrl/bs masks into a single vpmovmskb, regressing parse/ascii/eager by ~24% on Zen 2 (port-0-only vpmovmskb at 1/cycle was the bottleneck). Fix: in the inner loop, build a 'cb_v = ctrl|bs' vector first, then detect any interesting byte (ctrl|bs|high) via vpor(cb_v, chunk_raw) followed by a single vpmovmskb. Only on the slow path do we compute a second movemask on cb_v to disambiguate Tier 2 (pure UTF-8) from Tier 3 (control byte or backslash). ASCII inner loop: back to 1 vpmovmskb per chunk. CJK / mixed / emoji paths unchanged (they take the slow path on every chunk, but the single extra movemask there is dwarfed by lookup4's cost). Cross-check property test still passes 2000 cases against scalar. --- src/validate/strings/avx2.rs | 37 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/validate/strings/avx2.rs b/src/validate/strings/avx2.rs index 3534da9..ee923ab 100644 --- a/src/validate/strings/avx2.rs +++ b/src/validate/strings/avx2.rs @@ -238,33 +238,40 @@ unsafe fn validate_span_avx2_impl(span: &[u8]) -> Result<(), qjson_err> { while i + 32 <= n { let chunk = _mm256_loadu_si256(span.as_ptr().add(i) as *const __m256i); - let high = _mm256_movemask_epi8(chunk) as u32; - let ctrl = _mm256_movemask_epi8(_mm256_cmpgt_epi8( - _mm256_set1_epi8(0x20), - chunk, - )) as u32; - let bs = _mm256_movemask_epi8(_mm256_cmpeq_epi8( - chunk, - _mm256_set1_epi8(b'\\' as i8), - )) as u32; + // Build ctrl|bs vector; ORing with chunk raw lets one movemask capture + // high | ctrl | bs in a single shot (high-bit bytes are negative i8, + // so their top bit is already set in chunk). + let ctrl_v = _mm256_cmpgt_epi8(_mm256_set1_epi8(0x20), chunk); + let bs_v = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8(b'\\' as i8)); + let cb_v = _mm256_or_si256(ctrl_v, bs_v); + + // combined top bits: 1 iff byte is ctrl, backslash, OR ≥ 0x80. + // Single movemask extracts `high | ctrl | bs` for the Tier 1 fast check. + let combined = _mm256_or_si256(cb_v, chunk); + let any_interesting = _mm256_movemask_epi8(combined) as u32; // prev_ended_ascii: last byte of prior chunk had high bit clear, so no // cross-chunk multi-byte carry is pending. Without this guard, a lead // byte ending chunk N could cause lookup4 to miss its continuations in // chunk N+1. // Tier 1: pure printable ASCII, and previous chunk also ended ASCII-safe. - if (high | ctrl | bs) == 0 && prev_ended_ascii { + if any_interesting == 0 && prev_ended_ascii { prev_input = chunk; // prev_ended_ascii stays true (all bytes < 0x80) i += 32; continue; } + // Slow path: compute ctrl_bs_mask to distinguish Tier 2 from Tier 3. + // This second movemask is only reached when any_interesting != 0, so + // it does not appear on the ASCII hot path. + let ctrl_bs_mask = _mm256_movemask_epi8(cb_v) as u32; + // Tier 3: control byte or backslash → do NOT run lookup4 on this // chunk (the scalar path handles it correctly). First flush any UTF-8 // errors accumulated from prior Tier 2 chunks, then hand off to // scalar starting at the appropriate byte. - if (ctrl | bs) != 0 { + if ctrl_bs_mask != 0 { if _mm256_testz_si256(err_acc, err_acc) == 0 { return Err(qjson_err::QJSON_INVALID_UTF8); } @@ -279,19 +286,23 @@ unsafe fn validate_span_avx2_impl(span: &[u8]) -> Result<(), qjson_err> { return super::scalar::validate_span_scalar(&combined); } } + // high = any_interesting & ~ctrl_bs_mask (but we only need to know + // if any high-bit byte is present to decide the scalar start offset). + let high = any_interesting & !ctrl_bs_mask; // If there are no high-bit bytes in this chunk, bytes before the // first ctrl/bs are pure ASCII and need no validation — skip them. // If there ARE high-bit bytes, scalar must start from the beginning // of the chunk to correctly validate UTF-8 sequences. let start = if high == 0 { - i + (ctrl | bs).trailing_zeros() as usize + i + ctrl_bs_mask.trailing_zeros() as usize } else { i }; return super::scalar::validate_span_scalar(&span[start..]); } - // Tier 2: pure UTF-8 (no control, no backslash) → run lookup4. + // Tier 2: pure UTF-8 (no control, no backslash; high != 0 since + // any_interesting != 0) → run lookup4. err_acc = _mm256_or_si256( err_acc, lookup4_chunk(chunk, &mut prev_input, byte1_high_tbl, byte1_low_tbl, byte2_high_tbl),