Two companion commits ship HTTP response caching at both the VFS layer and the browser cc0 layer. sigil-fs daf6d3d adds http_cache.sg — a VFS-backed URL-keyed cache using FNV-1a hashing into /<pfx>/browser/cache/ files, with a 16-byte structured header, expiry via monotonic tick, and 64 KB body cap. sigil-apps 7f6501a adds fetch_cache.sg (T9 sprint) — a lightweight 16-slot in-memory cache at cache_buf @0x350000 using FNV-1a hash, circular eviction, and body data at body_buf @0x370000. FETCH-CACHE-PASS 8/8.
sigil-fs daf6d3d — VFS-backed HTTP cache
http_cache_init(pfx): creates /<pfx>/browser/cache/ namespace. Each cached URL maps to two VFS files: /<pfx>/browser/cache/<url_hash> (raw response body, up to 64 KB) and /<pfx>/browser/cache/<url_hash>.hdr (16-byte structured header). <url_hash> is the FNV-1a 32-bit hash of the URL string encoded as a 16-char hex filename (zero-padded). Header layout (16 bytes): status:u16 (HTTP status code), flags:u16 (bit 0 = is_expired), expires_seq:u32 (monotonic tick when entry expires; 0 = never), content_type_id:u8 (0=unknown, 1=text/html, 2=text/css, 3=application/js), body_len:u32, pad:u8×3.http_cache_put(pfx, url, status, ct_id, body, body_len, expires_seq): writes header file + body file; overwrites any existing entry. Body capped at 64 KB — truncated on put if larger. http_cache_get(pfx, url, tick, out_body, out_len): reads header; if expires_seq > 0 && tick > expires_seq, deletes both files and returns miss. Otherwise reads body into out_body. http_cache_has(pfx, url, tick): returns 1 if cached and not expired. http_cache_del(pfx, url): deletes both files. All operations are O(1) — hash-to-filename is constant time, VFS read/write is a single file op. ABCDEF pass: init, put, get (hit), get (miss after expiry), has, del+verify-gone.sigil-apps 7f6501a — browser fetch_cache (T9 sprint)
fetch_cache_init(): zeros all 16 slots at cache_buf @0x350000. Each slot is 32 bytes: +0: url_hash (Int, FNV-1a 32-bit, 0 = empty slot), +8: status (Int, HTTP status code), +16: body_off (Int, byte offset into body_buf @0x370000), +24: body_len (Int, byte length of body). next_slot pointer at cache_state @0x380000: circular eviction index, wraps at 16. str_buf @0x360000: URL string scratch space (hash computed over URL bytes here before slot lookup).fetch_cache_hash(url_ptr, url_len): FNV-1a 32-bit hash — seed 2166136261; for each byte: hash = (hash XOR byte) * 16777619, result masked to 32 bits. fetch_cache_put(url_ptr, url_len, status, body_ptr, body_len): hashes the URL; writes slot at next_slot; appends body to body_buf; increments next_slot mod 16. No de-duplication — a second put for the same URL overwrites via normal eviction. fetch_cache_get(url_ptr, url_len, out_status, out_body): linear scan of all 16 slots for matching hash; on match copies body from body_buf to out_body. fetch_cache_miss(url_ptr, url_len): returns 1 if no slot has matching hash.Test — FETCH-CACHE-PASS 8/8
| Test | Result |
|---|---|
| Init: all 16 slots empty | ✅ |
| Put /index.html (200, body "hello") | ✅ url_hash≠0, slot 0 filled |
| Put /style.css (200, body "body{}") | ✅ slot 1 filled |
| Put /404 (404, body "not found") | ✅ slot 2 filled |
| Get /index.html → status=200, body="hello" | ✅ |
| Get /style.css → status=200, body="body{}" | ✅ |
| Get /unknown → miss | ✅ |
| 13 more puts → overflow wraps, slot 0 evicted (next_slot=13) | ✅ |
FETCH-CACHE-PASS 8/8. 9744B arm-el0.
Layer relationship
fetch_cache.sg is the in-process cc0 cache (fastest path — no VFS syscall). http_cache.sg (sigil-fs) is the persistent VFS-backed cache (survives process restart, shared across tabs). The browser's fetch pipeline will call fetch_cache first; on miss, call the network stack; on network response, write to both fetch_cache (in-process) and http_cache (VFS persistent).
fetch pipeline
├── 1. fetch_cache_get() → in-process cc0 cache (@0x350000)
│ hit → return immediately (no syscall)
│ miss ↓
├── 2. network stack → HTTP request
│ response ↓
├── 3. fetch_cache_put() → in-process cc0 cache (16-slot circular)
└── 4. http_cache_put() → VFS persistent cache (//browser/cache/)