Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 286 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,292 @@ Important files:
When touching shared SIMD helpers, validate both parser and skip/on-demand
tests. If possible, also test `--//:sonic_dispatch=dynamic` on x86.

## SIMD Architecture Organization - Detailed Analysis

### Directory Structure Overview

```
include/sonic/internal/arch/
├── simd_dispatch.h # Central dispatch macro definitions
├── simd_base.h # Base function dispatch (TrailingZeroes, etc.)
├── simd_quote.h # Quote/string function dispatch
├── simd_skip.h # Skip scanner function dispatch
├── simd_itoa.h # Integer-to-string function dispatch
├── simd_str2int.h # String-to-integer function dispatch
├── sonic_cpu_feature.h # CPU feature detection macros
├── common/
│ ├── arm_common/ # Shared ARM implementations
│ │ ├── base.h # Shared bit manipulation (TrailingZeroes, etc.)
│ │ ├── simd.h # Shared to_bitmask() function
│ │ ├── quote.h # Shared Quote() implementation
│ │ ├── skip.inc.h # Shared skip implementations (template-based)
│ │ ├── itoa.h # Shared Utoa_8/Utoa_16 implementations
│ │ └── str2int.h # Shared simd_str2int (scalar fallback)
│ ├── x86_common/ # Shared x86 implementations
│ ├── quote_common.h # Scalar fallback for parseStringInplace/Quote
│ ├── quote_tables.h # Escape lookup tables (kEscapedMap, kQuoteTab*)
│ ├── skip_common.h # Shared EqBytes4, SkipLiteral, IsValidSeparator
│ └── unicode_common.h # Unicode handling (handle_unicode_codepoint, etc.)
├── neon/ # ARM NEON implementation
│ ├── base.h # Re-exports from arm_common
│ ├── simd.h # NEON-specific simd8/simd8x64 templates
│ ├── quote.h # NEON-specific parseStringInplace
│ ├── skip.h # NEON-specific SkipContainer, skip_space
│ ├── itoa.h # Re-exports from arm_common
│ ├── str2int.h # Re-exports from arm_common
│ └── unicode.h # NEON-specific StringBlock, GetNonSpaceBits
├── sve2-128/ # ARM SVE2-128 implementation
│ ├── base.h # Re-exports from arm_common
│ ├── simd.h # SVE2-specific type definitions (svuint8x16_t)
│ ├── quote.h # SVE2-specific parseStringInplace
│ ├── skip.h # SVE2-specific SkipContainer, skip_space
│ ├── itoa.h # Re-exports from arm_common
│ ├── str2int.h # SVE2-specific simd_str2int (optimized)
│ └── unicode.h # SVE2-specific StringBlock, GetNonSpaceBits
├── avx2/ # x86 AVX2 implementation
├── sse/ # x86 SSE implementation
└── x86_ifuncs/ # x86 dynamic dispatch (ifunc)
```

### Required Files Per Architecture Directory

Each architecture directory (`neon/`, `sve2-128/`, `avx2/`, `sse/`) must contain these files:

| File | Purpose |
|------|---------|
| `base.h` | Bit manipulation utilities (TrailingZeroes, ClearLowestBit, LeadingZeroes, CountOnes, PrefixXor, Xmemcpy, InlinedMemcmp, InlinedMemcmpEq) |
| `simd.h` | SIMD vector type definitions (`simd8<T>`, `simd8x64<T>`) with operator overloads, `to_bitmask()`, `eq()`, `lteq()` |
| `quote.h` | `parseStringInplace<ParseFlags>()` and `Quote<SerializeFlags>()` functions |
| `skip.h` | `SkipContainer()`, `skip_space()`, `skip_space_safe()`, plus includes `skip.inc.h` for `GetNextToken()`, `SkipString()`, `GetStringBits()` |
| `itoa.h` | `Utoa_8()` and `Utoa_16()` for integer-to-string conversion |
| `str2int.h` | `simd_str2int()` for string-to-integer conversion |
| `unicode.h` | `StringBlock` struct with `Find()`, `HasQuoteFirst()`, `HasBackslash()`, `HasUnescaped()`, `GetNonSpaceBits()` |

### Architecture Switching via Macro Definitions

The entire dispatch mechanism is controlled by two key macros defined in `simd_dispatch.h:21-55`:

**1. CPU Feature Detection (`sonic_cpu_feature.h:17-47`)**
```cpp
// x86 detection
#if defined(__SSE2__)
#define SONIC_HAVE_SSE
// ... SSE3, SSSE3, SSE4_1, SSE4_2, AVX, AVX2
#endif

// ARM detection
#if defined(__ARM_NEON) || defined(__ARM_NEON__)
#define SONIC_HAVE_NEON
#endif
#if defined(__ARM_FEATURE_SVE2) && (__ARM_FEATURE_SVE_BITS == 128)
#define SONIC_HAVE_SVE2_128
#endif
```

**2. Static Dispatch (`simd_dispatch.h:25-42`)**
```cpp
#if defined(SONIC_STATIC_DISPATCH)
// x86 priority: AVX2 > SSE
#if defined(SONIC_HAVE_AVX2)
#define SONIC_USING_ARCH_FUNC(func) using avx2::func
#define INCLUDE_ARCH_FILE(file) SONIC_STRINGIFY(avx2/file)
#elif defined(SONIC_HAVE_SSE)
#define SONIC_USING_ARCH_FUNC(func) using sse::func
#define INCLUDE_ARCH_FILE(file) SONIC_STRINGIFY(sse/file)
#endif

// ARM priority: SVE2-128 > NEON
#if defined(SONIC_HAVE_SVE2_128)
#define SONIC_USING_ARCH_FUNC(func) using sve2_128::func
#define INCLUDE_ARCH_FILE(file) SONIC_STRINGIFY(sve2-128/file)
#elif defined(SONIC_HAVE_NEON)
#define SONIC_USING_ARCH_FUNC(func) using neon::func
#define INCLUDE_ARCH_FILE(file) SONIC_STRINGIFY(neon/file)
#endif
```

**3. Dynamic Dispatch (`simd_dispatch.h:44-53`)**
```cpp
#elif defined(SONIC_DYNAMIC_DISPATCH)
#if defined(__x86_64__)
#define SONIC_USING_ARCH_FUNC(func) // Empty - uses ifunc resolution
#define INCLUDE_ARCH_FILE(file) SONIC_STRINGIFY(x86_ifuncs/file)
#elif defined(SONIC_HAVE_NEON)
#define SONIC_USING_ARCH_FUNC(func) using neon::func
#define INCLUDE_ARCH_FILE(file) SONIC_STRINGIFY(neon/file)
#endif
```

**4. Dispatch Header Pattern (e.g., `simd_base.h:17-36`)**
```cpp
#include "simd_dispatch.h"
#include INCLUDE_ARCH_FILE(base.h) // Includes e.g., "neon/base.h"

namespace sonic_json {
namespace internal {
SONIC_USING_ARCH_FUNC(TrailingZeroes); // Becomes: using neon::TrailingZeroes;
SONIC_USING_ARCH_FUNC(ClearLowestBit);
// ... more functions
} // namespace internal
} // namespace sonic_json
```

### SIMD Function Naming and Organization

**Namespace Hierarchy:**
```
sonic_json::internal::
├── common:: # Scalar fallbacks and cross-arch utilities
├── arm_common:: # Shared ARM implementations
├── x86_common:: # Shared x86 implementations
├── neon:: # NEON-specific implementations
├── sve2_128:: # SVE2-128-specific implementations
├── avx2:: # AVX2-specific implementations
├── sse:: # SSE-specific implementations
└── (direct) # Dispatched functions via using declarations
```

**Key Function Patterns:**

1. **SIMD Vector Types** (`simd.h`):
- `simd8<T>` - 16-byte vector (16 elements of type T)
- `simd8x64<T>` - 64-byte block (4 x simd8<T> chunks)
- Methods: `load()`, `store()`, `to_bitmask()`, `eq()`, `lteq()`, `reduce_or()`, operator overloads

2. **String Block Pattern** (`unicode.h`):
```cpp
struct StringBlock {
static StringBlock Find(const uint8_t* src); // Load and analyze 16 bytes
template<ParseFlags> bool HasQuoteFirst() const;
bool HasBackslash() const;
bool HasUnescaped() const;
int QuoteIndex() const;
int BsIndex() const;
uint64_t bs_bits, quote_bits, unescaped_bits; // NEON: bitmasks
// OR for SVE2: unsigned bs_index, quote_index, unescaped_index;
};
```

3. **String Parsing** (`quote.h`):
```cpp
template<ParseFlags parseFlags = kParseDefault>
sonic_force_inline size_t parseStringInplace(uint8_t*& src, SonicError& err);
```

4. **Skip Functions** (`skip.h`):
```cpp
sonic_force_inline bool SkipContainer(const uint8_t* data, size_t& pos,
size_t len, uint8_t left, uint8_t right);
sonic_force_inline uint8_t skip_space(const uint8_t* data, size_t& pos,
size_t&, uint64_t&);
```

### CPU Feature Detection

**Compile-time detection only** (no runtime CPUID for ARM):
- `sonic_cpu_feature.h` uses compiler predefined macros
- ARM: `__ARM_NEON`, `__ARM_FEATURE_SVE2`, `__ARM_FEATURE_SVE_BITS`
- x86: `__SSE2__`, `__AVX2__`, etc.
- No runtime feature detection for ARM (static dispatch only)
- x86 supports dynamic dispatch via `x86_ifuncs/` using GNU ifunc attribute

**Build system integration:**
- CMake: `-DENABLE_SVE2_128=ON` enables SVE2 compiler flags
- Bazel: `--//:sonic_arch={arm|sve2|haswell|westmere}`
- Bazel: `--//:sonic_dispatch={static|dynamic}`

### Common Code Organization

**Three levels of code sharing:**

1. **`common/*.h` - Architecture-independent:**
- `quote_common.h`: Scalar fallback `parseStringInplace()` and `Quote()` for x86 dynamic dispatch
- `skip_common.h`: `EqBytes4()`, `SkipLiteral()`, `IsValidSeparator()` - pure scalar
- `unicode_common.h`: `handle_unicode_codepoint()`, `codepoint_to_utf8()`, `hex_to_u32_nocheck()`, `GetEscaped()`
- `quote_tables.h`: `kEscapedMap[256]`, `kQuoteTabLowerCase[256]`, `kQuoteTabUpperCase[256]`, `kNeedEscaped[256]`

2. **`common/arm_common/*.h` - ARM-specific shared code:**
- `base.h`: `TrailingZeroes()` → `__builtin_ctzll()`, `ClearLowestBit()`, `LeadingZeroes()` → `__builtin_clzll()`, `CountOnes()` → `__builtin_popcountll()`, `PrefixXor()`, `Xmemcpy()`, `InlinedMemcmp()`, `InlinedMemcmpEq()`
- `simd.h`: `to_bitmask(uint8x16_t)` using NEON `vshrn_n_u16`
- `quote.h`: `Quote<SerializeFlags>()` using NEON intrinsics, shared by both NEON and SVE2-128
- `skip.inc.h`: Template implementations of `GetStringBits<T>()`, `GetNextToken<N>()`, `SkipString()`, `skip_container<T>()` - included with `#include` (not `using`)
- `itoa.h`: `UtoaNeon()`, `Utoa_8()`, `Utoa_16()` using NEON intrinsics
- `str2int.h`: Scalar fallback `simd_str2int()` (used by NEON, overridden by SVE2-128)

3. **Architecture-specific files (`neon/`, `sve2-128/`):**
- Thin wrapper headers that `using` declarations from `arm_common`
- Architecture-specific optimizations where needed:
- NEON: Full `simd8<T>` and `simd8x64<T>` template implementations
- SVE2-128: Minimal `simd.h` (reuses NEON simd types via `../neon/simd.h` in skip.h), optimized `simd_str2int()` using SVE2 `svdot`, predicate-based `StringBlock` using `svmatch`/`svbrkb`/`svcntp`

### Key Architectural Patterns

**1. Re-export Pattern (most files):**
```cpp
// neon/base.h
#include "../common/arm_common/base.h"
namespace neon {
using arm_common::TrailingZeroes;
using arm_common::ClearLowestBit;
// ...
}
```

**2. Template Include Pattern (`skip.inc.h`):**
```cpp
// neon/skip.h
#include "../common/arm_common/skip.inc.h" // Injects template functions directly

sonic_force_inline bool SkipContainer(...) {
return skip_container<simd8x64<uint8_t>>(data, pos, len, left, right);
}
```

**3. Cross-architecture Reuse (SVE2-128 using NEON):**
```cpp
// sve2-128/skip.h
#include "../neon/simd.h" // Reuse NEON simd types for skip_container

sonic_force_inline bool SkipContainer(...) {
// Use NEON implementation since it's faster for comparisons
return skip_container<neon::simd8x64<uint8_t>>(data, pos, len, left, right);
}
```

**4. Override Pattern (SVE2-128 str2int):**
```cpp
// sve2-128/str2int.h - does NOT include arm_common/str2int.h
// Instead provides optimized SVE2 implementation
sonic_force_inline uint64_t simd_str2int(const char* c, int& man_nd) {
// Uses svld1, svmatch, svdot, etc.
}
```

### NEON vs SVE2-128 Implementation Differences

| Aspect | NEON | SVE2-128 |
|--------|------|----------|
| Vector type | `uint8x16_t` | `svuint8x16_t` (fixed 128-bit SVE) |
| Load | `vld1q_u8(ptr)` | `svld1_u8(svptrue_b8(), ptr)` |
| Store | `vst1q_u8(ptr, v)` | `svst1_u8(svptrue_b8(), ptr, v)` |
| Compare | `vceqq_u8(v, dup)` → bitmask via `to_bitmask()` | `svmatch(ptrue, v, dup)` → predicate |
| Bitmask format | `uint64_t` (4 bits per lane) | `svbool_t` (predicate) |
| Find first | `TrailingZeroes(bitmask) >> 2` | `svcntp_b8(ptrue, svbrkb_z(ptrue, pmatch))` |
| StringBlock | Stores 3x `uint64_t` bitmasks | Stores 3x `unsigned` indices (0-15) |
| str2int | Scalar loop from arm_common | Optimized SVE2 `svdot` implementation |
| SkipContainer | Uses `neon::simd8x64` | Also uses `neon::simd8x64` (faster for comparisons) |

### Build Configuration Matrix

| Bazel Flag | CMake Equivalent | Effect |
|------------|------------------|--------|
| `--//:sonic_arch=arm` | (default on ARM) | Enables NEON |
| `--//:sonic_arch=sve2` | `-DENABLE_SVE2_128=ON` | Enables SVE2-128 (higher priority than NEON) |
| `--//:sonic_arch=haswell` | (default on x86) | Enables AVX2 |
| `--//:sonic_arch=westmere` | (manual) | Enables SSE4.2 |
| `--//:sonic_dispatch=static` | (default) | Static dispatch via macros |
| `--//:sonic_dispatch=dynamic` | (manual) | Dynamic dispatch via ifunc (x86 only) |

## Coding Style

- Follow root `.clang-format` (`BasedOnStyle: Google`).
Expand Down
15 changes: 15 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ sse_copts = ['-msse', '-msse2', '-msse4.1', '-msse4.2','-mpclmul']
arm_copts = ['-march=armv8-a']
apple_silicon_copts = [ '-mcpu=apple-m1' ]
sve2_copts = ['-march=armv8-a+sve2', '-msve-vector-bits=128']
riscv64_copts = ['-march=rv64gc']
riscv64_zbb_copts = ['-march=rv64gc_zbb']
default_copts = ['-march=native']
benchmark_copts = ['-O3', '-DNDEBUG', '-std=c++17']
static_dispatch_copts = []
Expand All @@ -29,6 +31,7 @@ filesystem_linkopts = select({
string_flag(
name = "sonic_arch",
build_setting_default = "default",
values = ["default", "haswell", "westmere", "arm", "sve2", "riscv64", "riscv64_zbb"],
)

string_flag(
Expand Down Expand Up @@ -60,6 +63,16 @@ config_setting(
flag_values = {":sonic_arch": "sve2"},
)

config_setting(
name = "riscv64_build",
flag_values = {":sonic_arch": "riscv64"},
)

config_setting(
name = "riscv64_zbb_build",
flag_values = {":sonic_arch": "riscv64_zbb"},
)

config_setting(
name = "apple_silicon_build",
values = {"cpu": "darwin_arm64"},
Expand Down Expand Up @@ -103,6 +116,8 @@ auto_arch_copts = select({
"sse_build": sse_copts,
"avx2_build": avx2_copts,
"apple_silicon_build": apple_silicon_copts,
"riscv64_build": riscv64_copts,
"riscv64_zbb_build": riscv64_zbb_copts,
"//conditions:default": default_copts,
}) + common_copts

Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ option(BUILD_UNITTEST "Build unittest." ON)
option(BUILD_FUZZ "Build fuzz." OFF)
option(BUILD_BENCH "Build benchmark." OFF)
option(ENABLE_SVE2_128 "Build for Arm SVE2 with 128 bit vector size" OFF)
option(ENABLE_RISCV_ZBB "Enable RISC-V Zbb extension for bit manipulation optimizations" OFF)

set(CMAKE_CXX_EXTENSIONS OFF)
if(BUILD_UNITTEST)
Expand Down
6 changes: 6 additions & 0 deletions cmake/set_arch_flags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ function(set_arch_flags target arch)
target_compile_options(${target} PRIVATE -march=armv8-a)
endif()
endif()
elseif(arch MATCHES "riscv64|riscv")
if(ENABLE_RISCV_ZBB)
target_compile_options(${target} PRIVATE -march=rv64gc_zbb)
else()
target_compile_options(${target} PRIVATE -march=rv64gc)
endif()
else()
message(FATAL_ERROR "Unsupported architecture: ${arch}")
endif()
Expand Down
3 changes: 2 additions & 1 deletion include/sonic/allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ class SpinLock {
__builtin_ia32_pause();
#elif defined(__aarch64__) || defined(_M_ARM64)
asm volatile("yield");
#elif defined(__riscv)
__asm__ volatile("pause");
#endif

#endif
}
}
Expand Down
Loading