Counting Memory Page Reads And Writes In The QIP Interpreter

qip-interpret-memory-page-writes-csv.wasm runs a QIP Content component in the bounded Zig interpreter, then returns one CSV row for each 64 KiB memory page that was initialized, read, or written. Use it to find where a component spends its reads and writes: data tables, output buffers, the shadow stack, or scratch memory.

make -j qip application/wasm/qip-interpret-memory-page-writes-csv.wasm

qip run \
  -F component=@text/markdown/commonmark.0.31.2.wasm \
  -F 'input=# Hello

Some *text* with [a link](https://example.com).' \
  application/wasm/qip-interpret-memory-page-writes-csv.wasm

The output for this request starts like this:

page,region,initialized_bytes,reads,bytes_read,writes,bytes_written,distinct_bytes_written,rewrite_ratio
15,stack,0,935,3348,700,2888,415,6.96
16,data+input,43776,641,1825,7,28,4,7.00
48,input+output,0,400,1336,144,176,88,2.00
80,other,0,23,92,4,16,16,1.00

The request uses the same component, input, and uniforms[<key>] fields and the same instruction_budget uniform as the QIP Content interpreter.

Columns #

A page is labelled by overlap only. A page labelled data can also hold heap bytes after the end of the segment.

What counts as a read or write #

The read and write counters cover the target's uniform setters and its render call. Copying data segments and the request input into memory before the run counts only toward initialized_bytes. Pages that were not initialized, read, or written get no row.

A malformed request, unsupported Wasm feature, trap, exhausted budget, invalid target Content contract, or target rejection becomes an outer Content rejection. The tool does not report pages for a run that did not finish.

When not to use it #

Page totals do not show when the component wrote each byte or which instructions produced it. Use qipdb to step through instructions and inspect memory as the component runs. The interpreter runs much slower than qip run, so measure speed with benchmarks, not with this tool.