← Blog
0.7.0 · FS · APPS · BROWSER

HTTP Cache — VFS-Backed Store (sigil-fs) + Browser Fetch Cache (sigil-apps T9)

June 22, 2026 · sigil-fs daf6d3d + sigil-apps 7f6501a · Sigil-Docs
browser fs cache http 0.7.0

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

Storage layout
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.
O(1) put/get/has/del + expiry
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)

16-slot cache at cache_buf @0x350000
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).
FNV-1a hash + circular eviction
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

TestResult
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/)