Files
Sylpheed/tools/ppc-manual/vmx/lvsl.md
sim 9bfe96e44d fix(ppc-manual): 543 dead links, from two generator bugs and wrong relative paths
- Category pages linked each family as `<slug>.md`, relative to categories/,
  where no family page lives. They now link `../<category>/<slug>.md`.
- Form pages linked a member into its *own* category directory, so every
  VMX128 sibling (`vsldoi128`) pointed at vmx128/ although its family page is
  under vmx/. They now link into the family's directory.
- Hand-written "Related" and sibling mentions linked other categories' pages
  as if they were in the same directory. 109 are retargeted through the page
  index; 29 that pointed a family page at itself (`vrefp128` on vrefp.md) and
  6 naming instructions the manual has no page for are plain text now.

Regenerated at the existing Canary pin (f21ebd49e): upstream has moved on, and
re-pinning belongs in its own change. The generator reports 0 family pages
changed and is idempotent; the only dead links left are TEMPLATE.md's
placeholders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:37:12 +02:00

189 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `lvsl` — Load Vector for Shift Left Indexed
> **Category:** [VMX (Altivec)](../categories/vmx.md) · **Form:** [X](../forms/X.md) · **Opcode:** `0x7c00000c`
<!-- GENERATED: BEGIN -->
## Assembler Mnemonics
| Mnemonic | XML entry | Flags | Description |
| --- | --- | --- | --- |
| `lvsl` | `lvsl` | — | Load Vector for Shift Left Indexed |
| `lvsl128` | `lvsl128` | — | Load Vector for Shift Left Indexed 128 |
## Syntax
```asm
lvsl [VD], [RA0], [RB]
lvsl128 [VD], [RA0], [RB]
```
## Encoding
### `lvsl` — form `X`
- **Opcode word:** `0x7c00000c`
- **Primary opcode (bits 0–5):** `31`
- **Extended opcode:** `6`
- **Synchronising:** no
| Bits | Field | Meaning |
| --- | --- | --- |
| 0–5 | `OPCD` | primary opcode |
| 6–10 | `RT/FRT/VRT` | destination |
| 11–15 | `RA/FRA/VRA` | source A |
| 16–20 | `RB/FRB/VRB` | source B |
| 21–30 | `XO` | extended opcode (10 bits) |
| 31 | `Rc` | record-form flag |
### `lvsl128` — form `VX128_1`
- **Opcode word:** `0x10000003`
- **Primary opcode (bits 0–5):** `4`
- **Extended opcode:** `3`
- **Synchronising:** no
| Bits | Field | Meaning |
| --- | --- | --- |
| 0–5 | `OPCD` | primary opcode (4) |
| 6–10 | `VD128l` | destination low 5 bits |
| 11–15 | `RA` | address register |
| 16–20 | `RB` | offset register |
| 21–27 | `XO` | extended opcode |
| 28–29 | `VD128h` | destination high 2 bits |
| 30–31 | `—` | reserved |
## Operands
| Field | Role | Description |
| --- | --- | --- |
| `RA0` | lvsl: read; lvsl128: read | Source GPR; when the encoded register number is 0 the operand is the literal 64-bit zero, **not** `r0`. |
| `RB` | lvsl: read; lvsl128: read | Source GPR. |
| `VD` | lvsl: write; lvsl128: write | Destination vector register. |
## Register Effects
### `lvsl`
- **Reads (always):** `RA0`, `RB`
- **Reads (conditional):** _none_
- **Writes (always):** `VD`
- **Writes (conditional):** _none_
### `lvsl128`
- **Reads (always):** `RA0`, `RB`
- **Reads (conditional):** _none_
- **Writes (always):** `VD`
- **Writes (conditional):** _none_
## Status-Register Effects
_No condition-register or status-register effects._
## Operation (pseudocode)
```
addr_lo <- ((RA|0) + (RB))[60:63]
for i in 0..15: VD[i] <- addr_lo + i
```
## C Translation Example
```c
/* lvsl VD, RA, RB — load-shift-left permute control */
uint64_t base = (insn.RA == 0) ? 0 : r[insn.RA];
uint8_t sh = (uint8_t)((base + r[insn.RB]) & 0xF);
for (int i = 0; i < 16; ++i) v[insn.VD].b[i] = sh + i;
```
## Implementation References
**`lvsl`**
- Canary XML: [`tools/ppc-instructions.xml` — search for `mnem="lvsl"`](https://github.com/xenia-canary/xenia-canary/blob/f21ebd49e979e44f081f474df78c3fbfee9cb3f2/tools/ppc-instructions.xml)
- Canary emitter: [`src/xenia/cpu/ppc/ppc_emit_altivec.cc:111`](https://github.com/xenia-canary/xenia-canary/blob/f21ebd49e979e44f081f474df78c3fbfee9cb3f2/src/xenia/cpu/ppc/ppc_emit_altivec.cc#L111)
- Sylpheed opcode: [`crates/sylpheed-ppc/src/opcode.rs:148`](../../../crates/sylpheed-ppc/src/opcode.rs#L148)
- Sylpheed decoder: [`crates/sylpheed-ppc/src/decoder.rs:866`](../../../crates/sylpheed-ppc/src/decoder.rs#L866)
<details><summary>Canary emitter (frozen snapshot @ <code>f21ebd49e9</code>)</summary>
```cpp
int InstrEmit_lvsl(PPCHIRBuilder& f, const InstrData& i) {
return InstrEmit_lvsl_(f, i, i.X.RT, i.X.RA, i.X.RB);
}
// ── delegates to (src/xenia/cpu/ppc/ppc_emit_altivec.cc:103) ──
int InstrEmit_lvsl_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd,
uint32_t ra, uint32_t rb) {
Value* ea = CalculateEA_0(f, ra, rb);
Value* sh = f.Truncate(f.And(ea, f.LoadConstantInt64(0xF)), INT8_TYPE);
Value* v = f.LoadVectorShl(sh);
f.StoreVR(vd, v);
return 0;
}
```
</details>
**`lvsl128`**
- Canary XML: [`tools/ppc-instructions.xml` — search for `mnem="lvsl128"`](https://github.com/xenia-canary/xenia-canary/blob/f21ebd49e979e44f081f474df78c3fbfee9cb3f2/tools/ppc-instructions.xml)
- Canary emitter: [`src/xenia/cpu/ppc/ppc_emit_altivec.cc:114`](https://github.com/xenia-canary/xenia-canary/blob/f21ebd49e979e44f081f474df78c3fbfee9cb3f2/src/xenia/cpu/ppc/ppc_emit_altivec.cc#L114)
- Sylpheed opcode: [`crates/sylpheed-ppc/src/opcode.rs:149`](../../../crates/sylpheed-ppc/src/opcode.rs#L149)
- Sylpheed decoder: [`crates/sylpheed-ppc/src/decoder.rs:527`](../../../crates/sylpheed-ppc/src/decoder.rs#L527)
<details><summary>Canary emitter (frozen snapshot @ <code>f21ebd49e9</code>)</summary>
```cpp
int InstrEmit_lvsl128(PPCHIRBuilder& f, const InstrData& i) {
return InstrEmit_lvsl_(f, i, VX128_1_VD128, i.VX128_1.RA, i.VX128_1.RB);
}
// ── delegates to (src/xenia/cpu/ppc/ppc_emit_altivec.cc:103) ──
int InstrEmit_lvsl_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd,
uint32_t ra, uint32_t rb) {
Value* ea = CalculateEA_0(f, ra, rb);
Value* sh = f.Truncate(f.And(ea, f.LoadConstantInt64(0xF)), INT8_TYPE);
Value* v = f.LoadVectorShl(sh);
f.StoreVR(vd, v);
return 0;
}
```
</details>
<!-- GENERATED: END -->
## Extended Pseudocode
```
; lvsl VD, RA, RB — load vector for shift left (generates a permute mask)
EA <- (RA|0) + (RB) ; full 64-bit EA; only the low 4 bits matter
sh <- EA[60:63] ; bits 60..63 of EA (the misalignment)
for i in 0..15:
VD[i] <- sh + i ; bytes 0..15 of VD = {sh, sh+1, …, sh+15}
```
## Special Cases & Edge Conditions
- **No memory is actually read.** Despite the name, `lvsl` / `lvsr` do **not** touch memory. They consume the effective address only to extract the low four bits (the alignment offset) and materialise a 16-byte permute control vector in `VD`. They are pure "address → permute-mask" converters.
- **Big-endian byte indexing.** `VD[0]` is the most-significant byte of the 128-bit register (lane 0). When `EA & 0xF == 0` the output is `{0, 1, 2, …, 15}`, i.e. the identity permute. When `EA & 0xF == 3` the output is `{3, 4, …, 18}` — modulo nothing, the values *do* exceed 15. That's intentional: fed into [`vperm`](vperm.md) (`vperm VD, VA, VB, VC`), byte selectors 0..15 index into `VA` and 16..31 index into `VB`. A stream of `lvsl` + two aligned `lvx` loads of consecutive 16-byte blocks + `vperm` reconstructs the unaligned 16-byte vector at `EA`.
- **Pair with [`lvsr`](lvsr.md) for the opposite direction.** `lvsl` shifts "left" (toward the low index / high address byte); `lvsr` shifts "right". Which one to pick depends on which aligned block you're starting from — see the idiom below.
- **Standard unaligned-load idiom.**
```
lvx vAL, r0, rA ; aligned block at EA & ~0xF
lvx vAH, r0, rA + 16 ; next aligned block
lvsl vC, r0, rA ; permute mask from misalignment
vperm vD, vAL, vAH, vC ; the unaligned 16 bytes starting at EA
```
- **`RA0` semantics.** When `RA = 0` the base is the literal zero, so `lvsl vD, 0, rB` derives the mask from `rB & 0xF`.
- **VMX128 sibling (`lvsl128`).** Same semantics; only the `VD` register is encoded with the 7-bit VMX128 register-fusion (`VD128l ‖ VD128h`) so `vD` may be `v0..v127`.
- **No flags, no side effects** beyond writing `VD`. Trivial to move and schedule.
## Related Instructions
- [`lvsr`](lvsr.md) — the mirror: `VD[i] = 16 − sh + i`.
- [`vperm`](vperm.md) — consumes the mask to perform arbitrary byte-level permutation across two vectors.
- [`lvx`](../memory/lvx.md), [`lvlx`](../memory/lvlx.md), [`lvrx`](../memory/lvrx.md) — the actual memory loads used alongside the mask.
- [`vsldoi`](vsldoi.md) — static-offset shift-double; when the shift is compile-time known, this is cheaper than the `lvsl`/`vperm` pair.
## IBM Reference
- [AIX 7.3 — `lvsl` (Load Vector for Shift Left Indexed)](https://www.ibm.com/docs/en/aix/7.3.0?topic=set-lvsl-load-vector-shift-left-indexed)
- [IBM AltiVec Technology Programmer's Interface Manual — unaligned-load idiom](https://www.nxp.com/docs/en/reference-manual/ALTIVECPIM.pdf)