libctype — CSTUB Custom-Type Functions
Part 14 · The ISS as Executable Oracle. This page documents
libctype.so, the lowest layer of the ISS type system: the host-callable C-stub implementations of the codec's custom register-value types — the get / set / move / convert (loadi/storei/ move /rtor) accessors for the 64 ctypes. It sits beneath the cas/fiss ISS, the codec, and the native TIE simulator. The fiss value leaves (fiss-datapath-oracle.md) operate on the ctype-typed values that this library marshals; the ctype/coproc/funcUnit ISA declarations live in../isa/core/ctype-coproc-funcunit.md, and the scalar-type/dtype mapping in../abi/scalartype-dtype-rosetta.md. The capstone that ties the whole oracle together isiss-oracle-synthesis.md.
0. What libctype is — and what it is not
libctype.so is the host-side value-marshalling layer for the Vision-Q7
GPSIMD config (the ncore2gp build). For every ctype the ISA declares — a scalar
AR uint32, a 1-bit predicate vbool1, a 512-bit vec2Nx8, a 1536-bit wvec
spill slot — there is a host representation (a small struct of consecutive
uint32 words, the "register representation") and a host-simulated target
memory window. libctype is the glue that copies a value between those two: it
loads a value out of simulated target memory into the host reg-struct, stores
it back, and converts a value of one ctype into another (widen / narrow /
splat / sign-extend / reinterpret).
It is a self-contained layer. Its only dynamic imports are libc:
$ nm -D libctype.so | rg ' U '
U exit@GLIBC_2.2.5
U fprintf@GLIBC_2.2.5
U malloc@GLIBC_2.2.5
U stderr@GLIBC_2.2.5
It does not import or link libisa-core, libcas-core, or libfiss-base,
and nothing in ncore2gp/config imports it (nm -D | rg ' U cstub_' is empty
across the configs). The consumers — the only objects that reference the
cstub_*_function symbols — are the native TIE simulator
(XtensaTools/lib/tc/nativesim) and the debugger (XtensaTools/bin/xt-gdb).
libctype is therefore orthogonal to the fiss/cas value+timing oracle: cas/fiss
compute values and timing; libctype marshals typed values across the
host↔target memory boundary for the native sim. [OBSERVED · HIGH]
NOTE. libctype is generated by the Xtensa TIE compiler from the same ctype/proto definitions that produce the ISA tables. The dispatch mechanism (3 fn-ptr tables indexed by ctype id, sorted convert lists, a
base_addrmemory window, a single/multi-word flag, an alignment hard-fault) is the fixedVERS_1.1TIE-cstub ABI; only the populated tables differ per config.
0.1 Binary identity
| property | value | source |
|---|---|---|
| path | ncore2gp/config/libctype.so | corpus |
| type | ELF64 LSB x86-64 .so, not stripped | file |
| size | 388,648 B | stat |
| sha256 | eb79ff…a3f91c | sha256sum |
| soname version | VERS_1.1 | readelf -V |
fn symbols (.symtab) | 315 (nm | rg -c ' [Tt] ') | nm |
[OBSERVED · HIGH]
0.2 Section→file map (VMA vs file offset)
$ readelf -SW libctype.so | rg '\.(text|rodata|data|bss)\b'
[12] .text PROGBITS 00000000000006d0 0006d0 0486a4 AX
[14] .rodata PROGBITS 0000000000048d80 048d80 0095b0 A
[23] .data PROGBITS 0000000000256040 056040 000240 WA
[24] .bss NOBITS 0000000000256280 056280 000680 WA
| section | VMA | file off | delta | holds |
|---|---|---|---|---|
.text | 0x6d0 | 0x6d0 | 0 | code: 6 dispatchers + 299 Function_TIE_* + 2 helpers |
.rodata | 0x48d80 | 0x48d80 | 0 | the splat/lane LUTs (cstub_Xm_*_table) + the unalign error format string @ 0x48d80 |
.data | 0x256040 | 0x056040 | 0x200000 | cstub_ctype_multi_word[64] @ file 0x56080 |
.bss | 0x256280 | — (NOBITS) | — | the 4 runtime-init globals |
GOTCHA.
.textand.rodataare VMA == file-offset, soobjdump --start-address=<VMA>andxxd -s <VMA>work directly..datais not — VMA − file =0x200000(the gpsimd config-DLL delta, not libtpu's0x400000). Toxxdthecstub_ctype_multi_wordtable at VMA0x256080you must read file offset0x56080. Always confirm per-section withreadelf -SW.
1. The runtime globals + the static size table
nm -S gives exact placement and span:
$ nm -S libctype.so | rg 'base_addr|ftable|frtor|multi_word'
0000000000256288 0000000000000008 b base_addr
0000000000256080 0000000000000040 d cstub_ctype_multi_word
00000000002562c0 0000000000000200 b cstub_loadi_ftable
00000000002564c0 0000000000000200 b cstub_storei_ftable
00000000002566c0 0000000000000008 b frtor
0000000000256700 0000000000000200 b cstub_rtor_ftable
| symbol | VMA | span | role |
|---|---|---|---|
base_addr | 0x256288 | 8 | host pointer to the simulated target-memory window |
cstub_loadi_ftable | 0x2562c0 | 0x200 = 64×8 | one fn-ptr per ctype id (0 if not installed) |
cstub_storei_ftable | 0x2564c0 | 0x200 = 64×8 | one fn-ptr per ctype id |
frtor | 0x2566c0 | 8 | scratch: current fn ptr during the inlined rtor append |
cstub_rtor_ftable | 0x256700 | 0x200 = 64×8 | one list-head per ctype id |
cstub_ctype_multi_word | 0x256080 | 0x40 = 64 | static const u8[64] size/width flag (only .data const) |
All four *_ftable/scalar globals are in .bss (zero at load), so
cstub_ctypes_init must run before any dispatcher returns a valid pointer.
Every table is 64 entries, indexed by ctype id 0..63 — the same 64-row
index space the ISA's ctypes[] table uses. [OBSERVED · HIGH]
1.1 cstub_ctype_multi_word[64] — the "value spans > 1 host word" flag
cstub_ctype_size_function(idx) is a one-line movzbl of this byte array (§2.5).
Reading the 64 bytes byte-exact (file off 0x56080):
$ xxd -s 0x56080 -l 64 libctype.so
0x56080: 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 00
0x56090: 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01
0x560a0: 01 01 ... (all 01 through idx 63)
| idx range | ctypes | flag |
|---|---|---|
| 0..5 | uint8 int8 uint16 int16 uint32 int32 | 0 (single host word) |
| 6..7 | uint64 int64 (scalar AR pair) | 1 (multi-word) |
| 8..17 | char short unsigned int void xtbool xtbool2..16 | 0 (single word) |
| 18..63 | all 46 Vision SIMD valign / vbool* / vec* / wvec / b32_pr / gvr | 1 (multi-word) |
The only scalar multi-word types are uint64/int64 (two 32-bit AR words); every
Vision SIMD type (≥ 16 bits, all wider than one host word) is multi-word; the
booleans and 32-bit scalars are single-word. The native sim reads this flag to
choose the reg-struct copy width / spill path. [byte values OBSERVED · HIGH; role INFERRED from the fn name and the loadi widths · MED]
2. The six exported dispatchers
The exported surface is exactly the libctype.def EXPORTS list, gated by the
VERS_1.1 script:
$ nm -D libctype.so | rg ' T cstub_'
0000000000043850 T cstub_set_base_address@@VERS_1.1
0000000000043860 T cstub_loadi_function@@VERS_1.1
0000000000043870 T cstub_storei_function@@VERS_1.1
0000000000043880 T cstub_rtor_function@@VERS_1.1
00000000000438c0 T cstub_ctype_size_function@@VERS_1.1
00000000000438d0 T cstub_ctypes_init@@VERS_1.1
Six exports, six lines in libctype.def, six global: entries in
libctype.map. Everything else is local: * (the 299 Function_TIE_* stubs and
the two helpers cstub_vaddr_not_aligned @ 0x6d0 and cstub_write_rtor_func
@ 0x3d4d0). [OBSERVED · HIGH]
2.1 cstub_set_base_address(void *ptr) @ 0x43850
43850: mov %rdi,base_addr(%rip) ; base_addr = ptr
43857: ret
Stores the host pointer to the simulated target-memory window. Every loadi/
storei forms its host address as base_addr + (esi + edx), so the (esi,edx)
pair is a 32-bit target virtual address split into {base_reg, offset} that
base_addr maps into the host address space. This is the host↔target memory
bridge. [OBSERVED · HIGH]
2.2 The ftable stride — cstub_loadi_function / cstub_storei_function
; cstub_loadi_function(uint32 idx) @0x43860
43860: lea cstub_loadi_ftable(%rip),%rax ; &table
43867: mov %edi,%edi ; idx zero-extended to 64 bits
43869: mov (%rax,%rdi,8),%rax ; rax = table[idx] <-- STRIDE 8
4386d: ret
cstub_storei_function @ 0x43870 is byte-identical on cstub_storei_ftable.
The ftable stride is 8 bytes. The index expression
(%rax,%rdi,8)is a scale-8 SIB:table[idx]lives at&table + idx*8— one 8-byte function pointer per ctype id,idxzero-extended (mov %edi,%edi) before scaling so the top 32 bits cannot poison the address. There is no bounds check: the caller (the native sim) is trusted to passidx ∈ [0,63].[OBSERVED · HIGH]
The native sim calls this once per ctype to obtain the marshalling stub, then invokes the returned pointer.
2.3 cstub_rtor_function(uint32 srcidx, uint32 otheridx) @ 0x43880 — the convert lookup
The rtor table is indexed by the source ctype id (same stride 8), but each slot is a list head, not a function pointer. The dispatcher walks a singly-linked, sorted list keyed on the other (target) ctype id:
43880: lea cstub_rtor_ftable(%rip),%rax
43887: mov %edi,%edi
43889: mov (%rax,%rdi,8),%rax ; node = rtor_ftable[srcidx] (list head)
4388d: test %rax,%rax
4388f: je null ; empty list -> NULL
43892: mov (%rax),%edx ; edx = node->other_ctype_id (+0x00)
43894: cmp %esi,%edx
43896: je hit
43898: cmp %edx,%esi
4389a: jae loop_body
4389c: jmp null ; esi < head.other -> sorted miss, early-out
; loop:
438a0: cmp %esi,(%rax) ; node->other vs otheridx
438a2: je hit
438a4: ja null ; passed it (sorted) -> miss, early-out
438a6: mov 0x10(%rax),%rax ; node = node->next (+0x10)
438aa: test %rax,%rax
438ad: jne loop
438af: xor %eax,%eax ; NULL
438b1: ret
; hit:
438b8: mov 0x8(%rax),%rax ; return node->fn (+0x08)
438bc: ret
The list is kept sorted by other-type-id ascending: the cmp … ja null
short-circuits as soon as the walk passes the wanted id, so a miss costs O(degree)
in the worst case but bails early on the common case. Returns
Function_TIE_<src>_rtor_<other> for the (src,other) pair, or NULL.
[OBSERVED · HIGH]
2.4 The rtor convert NODE — 24 bytes, offsets read from disasm
CORRECTION — there is NO DWARF in this binary. Despite "not stripped", the ELF has only
.symtab/.strtab(function names +nm -Ssizes); there are zero.debug_*sections.addr2lineresolves a function name from.symtabbut reportsfile:?for every line (no.debug_line). The struct field offsets below are therefore pinned from the disassembly displacements themselves (the literalmovencodings in the node builder and the dispatcher walk) — which is stronger than DWARF, because it is the actual code that reads the fields. The0x18-byte node size is the literalmallocargument.
The builder cstub_write_rtor_func @ 0x3d4d0 (79 bytes) decodes:
3d4e7: mov (%r12,%rbp,8),%rbx ; rbx = rtor_ftable[srcidx] (r12=&table, rbp=src)
3d4eb: test %rbx,%rbx
3d4ee: jne walk ; non-empty -> walk to tail
3d4f0: jmp install_head ; empty -> install at head
; walk:
3d4f8: mov %rax,%rbx
3d4fb: mov 0x10(%rbx),%rax ; rax = node->next (+0x10)
3d4ff: test %rax,%rax
3d502: jne 3d4f8 ; ...to tail
3d504: mov $0x18,%edi
3d509: call malloc@plt ; n = malloc(24)
3d50e: mov %r14,0x8(%rax) ; n->fn = fn (+0x08) (r14 = rdi arg)
3d512: mov %r13d,(%rax) ; n->other = otheridx (+0x00) (r13d = edx arg, 32-bit)
3d515: movq $0x0,0x10(%rax) ; n->next = NULL (+0x10)
3d51d: mov %rax,0x10(%rbx) ; tail->next = n
; install_head (empty list):
3d543: mov %rax,(%r12,%rbp,8) ; rtor_ftable[srcidx] = n
struct cstub_rtor_node { /* 24 bytes — malloc(0x18) */
int32_t other_ctype_id; /* +0x00 the convert "to" ctype id */
/* +0x04: 4 bytes implicit padding (alignment of the void*) */
void *fn; /* +0x08 Function_TIE_<src>_rtor_<other> */
struct cstub_rtor_node *next; /* +0x10 NULL-terminated singly-linked list */
};
The offsets +0x00 / +0x08 / +0x10 and the size 0x18 are read directly from the
instruction encodings above, and they match the dispatcher walk in §2.3
(mov (%rax) reads +0x00, mov 0x8(%rax) returns +0x08, mov 0x10(%rax)
follows +0x10). cstub_write_rtor_func is the clean API the codec/generator
calls; cstub_ctypes_init inlines the same append logic ~265 times.
[OBSERVED · HIGH]
QUIRK.
other_ctype_idis a 32-bit field (mov %r13d,(%rax)) even though only 64 ctype ids exist, and the comparison in the dispatcher iscmp %esi,(%rax)(32-bit) — so the upper bytes of the host's 64-bit slot are never written and never read; they stay whatevermallocreturned. The 4-byte pad at+0x04exists purely to 8-align thefnpointer.
2.5 cstub_ctype_size_function(uint32 idx) @ 0x438c0
438c0: lea cstub_ctype_multi_word(%rip),%rax
438c7: mov %edi,%edi
438c9: movzbl (%rax,%rdi,1),%eax ; return (u8) multi_word[idx] <-- STRIDE 1
438cd: ret
GOTCHA — two different strides in one library. The three function tables are indexed scale-8 (
,%rdi,8, 8-byte pointers); the size table is au8[64]indexed scale-1 (,%rdi,1, one byte). Do not confuse the size-function's byte-array stride with the ftable's pointer stride.
Returns the single/multi-word flag of §1.1. [OBSERVED · HIGH]
3. cstub_ctypes_init — the ftable builder (4,715 instructions @ 0x438d0)
nm -S gives the span (0x54a4 bytes); disassembling that range yields exactly
4,715 instructions and exactly 265 call malloc sites:
$ nm -S libctype.so | rg ctypes_init
00000000000438d0 00000000000054a4 T cstub_ctypes_init
$ objdump -d --start-address=0x438d0 --stop-address=0x48d74 libctype.so | rg -c '^\s+[0-9a-f]+:'
4715
$ objdump -d --start-address=0x438d0 --stop-address=0x48d74 libctype.so | rg -c 'call.*malloc'
265
This is the one-shot "register all marshalling stubs" call the native sim issues
at startup, after cstub_set_base_address. It runs in four stages:
-
Stage A — zero-fill. Two
rep stos %rax,(%rdi)clear0x40qwords each intoloadi_ftableandstorei_ftable(both 64-slot tables to NULL). -
Stage B —
loadipopulate. For each installed ctype:lea Function_TIE_*_loadi; mov %rcx, off(%rsi)withoff = ctypeid * 8. The first writes, byte-exact:off idx fn 0x204 Function_TIE_xt_core_uint32_loadi0x9018 Function_TIE_xt_ivp32_valign_loadi0x9819 Function_TIE_xt_ivp32_vboolN_loadi0xa020 Function_TIE_xt_ivp32_vbool2N_loadi0xa821 Function_TIE_xt_ivp32_xb_vec2Nx8_loadi0xb022 Function_TIE_xt_ivp32_xb_vec2Nx8U_loadiThe 47 populated
loadiindices are exactly{4} ∪ {18..63}: onlyuint32among the scalar core types, plus every Vision SIMD type.loadiforuint8/int8/uint16/int16/char/short/unsigned/int/void/xtbool*is not installed — the sim narrows from theuint32loadi (or never immediate-loads those).[OBSERVED · HIGH] -
Stage C —
storeipopulate. 47storeiimpls arelea'd intostorei_ftable; all 47 exist as symbols (nm \| rg -c _storei= 47). -
Stage D —
rtorbuild (from0x43e14).rep stos $0clearsrtor_ftable(64 list heads), then for each(src,other)convert edge it walksrtor_ftable[src]to the tail,malloc(0x18), writes{other_id, fn, next=0}, and appends — the inlined form of §2.4. The 265malloc(0x18)calls = 265 convert edges; with 205 distinct convert functions (a function may be registered under several source lists) the convert graph has 265 edges over 205 functions.frtor@0x2566c0holds the current fn ptr during the inlined append.[OBSERVED · HIGH; the "sim-startup one-shot" role INFERRED · MED]
NOTE. In Stage B/C the populate stores target
%rsi/%rdx(registers holding the table base insidectypes_init), not the global symbol directly — the base pointers are loaded once at function entry. The decodedoffdisplacements are absolute byte offsets into the 64-slot table, sooff / 8recovers the ctype id.
4. Representative stub decodes — the get / set / move / convert bit-layouts
Calling convention (observed across all stubs):
loadi (reg_struct *dst rdi, uint32 base esi, uint32 off edx) -> writes *dst
storei(reg_struct *src rdi, uint32 base esi, uint32 off edx) -> reads *src (or edi)
rtor (reg_struct *dst rdi, src esi) ; esi = a scalar VALUE for scalar sources,
; a reg_struct* POINTER for wide sources
host address = base_addr + (esi + edx)
reg_struct = N consecutive uint32 words (the "register representation")
(a) Scalar AR — Function_TIE_xt_core_uint32_loadi @ 0x3d4a0 (ctype idx 4)
; loadi @0x3d4a0
3d4a0: add %esi,%edx ; vaddr = base + off
3d4a2: test $0x3,%dl ; (vaddr & 3) ?
3d4a5: jne fault ; -> cstub_vaddr_not_aligned (exit 1)
3d4a7: mov base_addr(%rip),%rax
3d4ae: and $0xfffffffc,%edx ; vaddr &= ~3
3d4b1: mov (%rdx,%rax,1),%eax ; load 4 bytes
3d4b4: mov %eax,(%rdi) ; *(uint32*)dst = word
3d4b6: ret
; storei @0x3d480 (same align check):
; *(uint32*)(base_addr + (vaddr & ~3)) = (uint32)edi
Canonical scalar-AR get/set: a 4-byte word with 4-byte alignment
ENFORCED — a misaligned address is an unconditional hard-fault. [OBSERVED · HIGH]
The fault path:
; cstub_vaddr_not_aligned(uint32 vaddr) @0x6d0
6d0: ... fprintf(stderr, FMT, vaddr); exit(1)
with FMT @ .rodata 0x48d80:
"Error: The config takes exception when the address of an load/store is unaligned. The address 0x"
[OBSERVED · HIGH]
(b) Predicate vbool1 — Function_TIE_xt_ivp32_vbool1_loadi @ 0x810 (ctype 49)
810: lea (%rsi,%rdx,1),%ecx ; vaddr = base + off
813: mov base_addr(%rip),%rax
81a: mov %ecx,%edx
81c: shl $0x3,%ecx ; vaddr << 3
81f: and $0xfffffffc,%edx ; word addr = vaddr & ~3
822: and $0x18,%ecx ; shift = (vaddr*8) & 0x18 = (vaddr&3)*8
825: mov (%rdx,%rax,1),%eax ; word = *(uint32*)(base_addr + word_addr)
828: movl $0x0,0x4(%rdi) ; *(dst+4) = 0 (clear upper slot word)
82f: shr %cl,%eax ; word >>= shift
831: and $0x1,%eax ; & 1
834: mov %eax,(%rdi) ; *(uint32*)dst = predicate bit (0/1)
836: ret
1-bit predicate get: select the byte lane by (vaddr&3)*8, extract bit 0,
normalize to {0,1} in a 32-bit host slot (and zero the upper slot word). storei
(0x1540) is the inverse: replicate bit 0 of *src to bit 0 of each byte lane,
select the lane, write one byte back. [OBSERVED · HIGH]
(c) Vector vec2Nx8 (512-bit) — loadi @ 0x3e7f0 / storei @ 0x3d5b0 (ctype 21)
3e7f0: lea (%rsi,%rdx,1),%eax
3e7f3: add base_addr(%rip),%rax
3e7fa: movdqu (%rax),%xmm3 ; 4x unaligned 16-byte loads
3e7fe: movdqu 0x10(%rax),%xmm2
3e803: movdqu 0x20(%rax),%xmm1
3e808: movdqu 0x30(%rax),%xmm0
3e80d: movups %xmm3,(%rdi) ; 4x writes -> 64-byte dst
3e810: movups %xmm2,0x10(%rdi)
3e814: movups %xmm1,0x20(%rdi)
3e818: movups %xmm0,0x30(%rdi)
3e81c: ret
Flat 64-byte memcpy (512 bits) between the host vec slot and target memory.
No alignment check — wide vectors use unaligned movdqu/movups. storei is
the exact inverse. [OBSERVED · HIGH]
QUIRK. Scalars hard-fault on misalignment (a); vectors silently accept any address via unaligned moves (c). The single/multi-word flag (§1.1) is the predicate that splits these two paths in the sim.
(d) Wide spill wvecspill (1536-bit) — Function_TIE_xt_ivp32_xb_wvecspill_loadi @ 0x41d90
$ nm -S libctype.so | rg wvecspill_loadi
0000000000041d90 000000000000045a t Function_TIE_xt_ivp32_xb_wvecspill_loadi
The largest stub — 0x45a = 1,114 bytes. It copies 192 bytes (1536 bits) from
base_addr+vaddr to *dst in three 0x40-byte chunks (offsets 0 / 0x40 /
0x80), moving DWORD-by-DWORD through GPRs and a stack staging area. This is how
the codec materialises the 1536-bit accumulator spill/reload host-side. [OBSERVED · HIGH]
(e) Lane int64 — Function_TIE_xt_ivp32_xb_int64_loadi @ 0x3dca0
3dca0: add %esi,%edx ; vaddr
3dca2: mov %edx,%eax
3dca4: shr $0x2,%edx
3dca7: and $0xffffffc7,%eax ; addr = vaddr & 0xffffffc7
3dcaa: add base_addr(%rip),%rax
3dcb1: and $0xe,%edx ; lane = (vaddr>>2) & 0xe
... ; copy the selected 64-bit lane
Lane-relative sub-addressing: it picks a 64-bit value within a vector lane
group, distinct from the scalar AR uint64 {hi,lo} 2-word form (ctype 6).
[OBSERVED · HIGH]
(f) Convert scalar→predicate — int32_rtor_xb_int32pr @ 0x113b0
113b0: mov %esi,(%rdi) ; *(int32*)dst = value
113b2: sar $0x1f,%esi ; sign = value >> 31
113b5: mov %esi,0x4(%rdi) ; *(int32*)(dst+4) = sign
113b8: ret
The b32_pr predicate is the {value, sign} 64-bit form. rtor here is a
register-value convert (src in esi). [OBSERVED · HIGH]
(g) Convert scalar→vector (widen/splat) — uint8_rtor_xb_vec2Nx8 @ 0x3070
3070: mov %esi,-0xc(%rsp)
3074: movd -0xc(%rsp),%xmm3
307a: movdqa cstub_Xm_ncore2gp_table__xt_bytelane_lsb+0x40(%rip),%xmm0
3082: pshufd $0x0,%xmm3,%xmm1 ; splat scalar across lanes
3087: pand %xmm1,%xmm0 ; byte-lane mask via the .rodata LUT
308f: pslld $0x18,%xmm2 ; + lane shifts (8/0x10/0x18)
3094: por %xmm2,%xmm0
... ; writes 64 bytes (4x movups) to *dst
Broadcasts the scalar esi across SIMD lanes using the .rodata LUT
cstub_Xm_ncore2gp_table__xt_bytelane_lsb (region near 0x52300) — the
scalar→vec widening convert. [OBSERVED · HIGH]
(h) Convert vector→scalar (narrow) — vec2Nx8_rtor_int8 @ 0x113c0
113c0: movsbl (%rsi),%eax ; sign-extend first byte of source VECTOR (rsi = ptr)
113c3: mov %eax,(%rdi) ; *(int32*)dst = (int8)src[0]
113c5: ret
The inverse of (g): src is a pointer (rsi) because the source is wide.
[OBSERVED · HIGH]
(i) Convert narrowing int32→int16 — int32_rtor_xb_int16 @ 0xf2a0
f2a0: and $0xffff,%esi ; value & 0xffff
f2a6: movl $0x0,0x4(%rdi) ; zero-fill the rest of the wide dst slot
f2ad: movl $0x0,0x8(%rdi)
f2b4: mov %esi,(%rdi) ; *(uint32*)dst = masked value
f2b6: movl $0x0,0xc(%rdi) ; ...
Narrow-with-zero-extend into a vec-width destination slot. [OBSERVED · HIGH]
Roles summary — the cstub's get / set / move / convert
| op | kind | host realisation |
|---|---|---|
| GET (extract value from regfile repr) | loadi | read target memory → host slot; sub-word types also extract the bit/byte field (vbool1 in (b)) |
| SET (insert value) | storei | write host slot → target memory; sub-word types pack the bit/byte lane |
| MOVE (same-type) | — (no fn) | a flat struct copy (vec→vec = 64-byte memcpy); the ISA has 47 move protos in the table, but the cstub realises them as the trivial copy, not a separate function |
| CONVERT (cross-type) | rtor | the 205 rtor stubs widen (scalar→vec splat (g)), narrow (vec→scalar (h), int32→int16 (i)), sign/zero-extend (→predicate (f)), reinterpret (vec↔vecU 64-byte copy); driven by the per-source sorted convert list (§2.3) |
[OBSERVED · HIGH]
5. Kind census — 299 Function_TIE_* and the proto-kind mapping
The library implements 299 Function_TIE_* stubs. Re-grounded against the
binary:
$ nm libctype.so | rg -c 'Function_TIE_' ; 299
$ nm libctype.so | rg -c 'Function_TIE_.*_rtor' ; 205
$ nm libctype.so | rg -c 'Function_TIE_.*_loadi' ; 47
$ nm libctype.so | rg -c 'Function_TIE_.*_storei' ; 47
$ for k in _mtor _rtom _move _loadx _storex _loadip _storeip _loadxp _storexp; do
nm libctype.so | rg -c "Function_TIE_.*$k"; done ; all 0
| kind | ctype-proto rows (the ISA TABLE) | libctype cstub fns | host realisation |
|---|---|---|---|
rtor | 205 | 205 | register VALUE convert stubs ✓ |
loadi | 47 | 47 | base+off load, align-checked (scalar) ✓ |
storei | 47 | 47 | base+off store ✓ |
move | 47 | 0 | trivial struct copy (no fn) |
mtor | 71 | 0 | TABLE only |
rtom | 64 | 0 | TABLE only |
loadip / storeip | 31 / 31 | 0 | host: loadi/storei + ptr update |
loadx / storex | 27 / 27 | 0 | host: loadi/storei with reg offset |
loadxp / storexp | 27 / 27 | 0 | not stubbed |
| sum | 651 | 299 |
The match is the authoritative link.
rtor 205 / loadi 47 / storei 47are exactly the corresponding rows of the ISA'sctype_protos[]table. The cstub is the host C implementation of precisely those three proto rows; the nine other kinds have zero host stubs (thefor-loop above prints0for every one). They live only in the libisa proto table (the instruction-sequence binding) — the native sim synthesises them caller-side: the+offinloadi/storeiis the immediate, and indexed / post-increment addressing modes are computed by the caller before it callsloadi/storei; same-type moves are a trivial struct copy.[count match OBSERVED · HIGH; the "synthesised caller-side" mechanism for the 9 absent kinds INFERRED · MED]
Ftable index == ctype id (the second cross-check): the populated loadi
entries decode byte-exact to the ISA ctype indices — off/8 gives
idx4=uint32, idx18=valign, idx19=vboolN, idx20=vbool2N, idx21=vec2Nx8,
idx22=vec2Nx8U, idx49=vbool1, idx58=int64(vec), idx60/61=int32pr/int64pr,
idx63=wvecspill — in exact agreement with the ctype/coproc ISA page. [OBSERVED · HIGH]
6. Relation to the ISS value oracle (does fiss/cas call libctype?)
No. libctype is an independent host type-system / value-marshalling layer:
libctype.sodynamic imports = libc only (malloc,fprintf,exit,stderr); it imports nocstub_/Function_TIE_from elsewhere, and no.soinncore2gp/configimports it (nm -D … U cstub_is empty across configs).libisa-core.soandlibcas-core.socontain zerocstub_/Function_TIE_symbols.- The
cstub_*_functionstrings appear inXtensaTools/lib/tc/nativesim(the native TIE instruction-set simulator) andXtensaTools/bin/xt-gdb(the debugger) — those are the consumers. nativesim resolvescstub_loadi_functionetc. and drives the per-ctype marshalling while executing TIE semantics on the host.
Division of labour:
libcas-core/libfiss-base— the cycle/datapath simulators: they compute values (the fiss xdref value leaves offiss-datapath-oracle.md) and timing (the per-operand pipeline stages / scoreboard). cas has its own_movesymbols (cas-internal — unrelated to the ctype cstub).libisa-core— the static ISA tables (formats/slots/regfiles/states/ctypes[64]/ctype_protos[651]): the declarations, including the proto names whose accessors libctype implements. See../isa/core/ctype-coproc-funcunit.mdand../abi/scalartype-dtype-rosetta.md.libctype— the host-side value codec: marshals a ctype value between the host reg-struct and the host-simulated target memory (base_addrwindow) for the native sim / debugger. It is the get/set/move/convert implementation, not the arithmetic oracle and not the timing model.
The capstone iss-oracle-synthesis.md folds these
three back together. [OBSERVED · HIGH]
7. Wide-type handling
| width | type(s) | host copy path |
|---|---|---|
| 512-bit (64 B) | vec | 4× movdqu/movups, no align check (§4c) |
| 1024 / 2048-bit | wvec | same flat SIMD copy, scaled (8/16 moves) |
| 1536-bit (192 B) | wvecspill | DWORD-granular GPR + stack copy in 3× 0x40 chunks — the heaviest stub, 0x45a B (§4d) |
| 64-bit lane | int64 (vec regfile) | lane-relative sub-addressing vaddr & 0xffffffc7, lane = (vaddr>>2)&0xe (§4e) |
| 64-bit AR | uint64/int64 (multi_word=1, 2 regs) | {hi,lo} 32-bit pair |
| sub-32-bit | vbool1 (1 b), int8/int16 | bit/byte-lane extract+pack on get/set; mask+zero-extend on convert (§4b, 4i) |
The cstub_ctype_multi_word[64] flag (§1.1) tells the sim which types need the
multi-word (> 1 host word) copy path: idx 6,7,18..63 = 1. [OBSERVED · HIGH]
8. Per-gen stability
- Only one
libctype.soships in the corpus (ncore2gp/config); no sibling per-gen copy exists (fd libctype.sofinds this binary + the IDA sidecars only).[OBSERVED · HIGH] - The structure is config-generated: the TIE compiler emits
ctypes_initand theFunction_TIE_*set from the same ctype/proto definitions that produced the 64-ctype / 651-proto ISA tables. Any gen with the same 64-ctype / 651-proto config yields the same 299-fn geometry (rtor205/loadi47/storei47,ftables[64], 24-byte rtor nodes). Adding/removing a ctype or a convert edge would shift the ftable index range and the rtor node count, but the dispatch mechanism — 3ftable[ctypeid]+ sorted convert list +base_addrwindow +multi_wordflag + align-fault — is gen-invariant: it is the fixed XtensaTools TIE-cstub ABI (VERS_1.1, the 6 exports inlibctype.def).[MED] - The exported ABI is a stable XtensaTools TIE interface; the generic
XtensaTools/lib/libxtctype.soshares the same export names (a stripped ~10 KB interface stub — not the populated table; the populated one is this file). So the dispatcher surface is portable across Xtensa configs; only the populated tables differ.[MED]
9. Confidence / uncertainty ledger
HIGH (proven from bytes/disasm):
- 6 exports + their disasm (
set_base_address,loadi/storei/rtor/size,ctypes_init); all six matchlibctype.def/libctype.map. - ftable geometry: 3×
.bss 0x200 = 64×8, stride 8, indexed by ctype id;cstub_ctype_multi_word .data[64]byte values;base_addr/frtorplacement. - 24-byte rtor node
{other_id@+0, fn@+8, next@+0x10}; sorted-list dispatch; 265 edges (rg -c call.*malloc= 265). - kind census
rtor205/loadi47/storei47and the 9-zero kinds — re-grounded bynm | rg -c; the count match to the ISA proto rows. loadiftable index decode byte-exact to the ISA ctype indices (idx 4, 18..63).- all §4 stub decodes with their masks/shifts/widths; the align-fault path
(
cstub_vaddr_not_aligned→fprintf+exit(1),&3check on scalars). - imports = libc only; no cas/isa/fiss linkage; consumers = nativesim + xt-gdb.
cstub_ctypes_init= 4,715 instructions (objdump count over the0x54a4span).
MED (strong inference):
- the 9 absent proto kinds are synthesised caller-side from
base_addrarithmetic (counts OBSERVED=0; the "caller computes addr/move" mechanism INFERRED). ctypes_initis the sim-startup one-shot (role INFERRED from.bss+ the consumer set).- per-gen invariance of the dispatch mechanism (single gen in corpus).
LOW: none material; every decoded fn / field / mask / count is anchored to read bytes.
CORRECTION (carried into §2.4). The task brief and the heading both say "libctype is NOT stripped, use DWARF" — but the binary has no
.debug_*sections at all (only.symtab/.strtab). All struct offsets here are pinned from the disassembly displacements, not fromaddr2line/DWARF (which returnsfile:?). The result is identical (and arguably stronger): the offsets are read from the literalmovencodings that touch the fields.
10. Closure — what this layer covers
libctype.so is the host value-marshalling floor beneath the codec / native
TIE simulator: the C-stub get(loadi) / set(storei) / move /
convert(rtor) implementations of the ISA ctype value types, with
- a 6-export
VERS_1.1dispatch ABI (libctype.def); - 3
ftables[64]indexed by ctype id at stride 8 (loadi 47 / storei 47 installed) + a per-source sorted rtor convert list (205 fns / 265 edges, 24-byte nodes); - a
base_addrhost↔target memory window + a 4-byte-align hard-fault on scalars; - the
cstub_ctype_multi_word[64]single/multi-word flag (idx6,7,18..63 = 1); - representative get/set/move/convert bit-layouts decoded (scalar AR, vbool1 predicate, 512-bit vec, 1536-bit wvec spill, lane int64, scalar↔vec convert);
- a proto-kind mapping byte-exact to the ISA
ctype_protosrows (rtor205/loadi47/storei47); the 9 other kinds are table-only, synthesised by the caller; - an independent host type-system layer (libc-only deps; consumed by nativesim/xt-gdb; not called by cas/fiss/isa) — orthogonal to the value+timing oracle.
Combined with the ctype/proto ISA tables
(../isa/core/ctype-coproc-funcunit.md),
the fiss/cas value+timing oracle
(fiss-datapath-oracle.md), and the dtype Rosetta
(../abi/scalartype-dtype-rosetta.md), the
ISS lane is near-complete; the validation harness (Part 15) cross-checks these
decodes against the live simulator.