Skip to content

bugc: keep a slice of calldata in calldata - #367

Merged
gnidan merged 3 commits into
mainfrom
bugc-calldata-slices
Oct 7, 2026
Merged

gnidan merged 3 commits into
mainfrom
bugc-calldata-slices

Conversation

@gnidan

@gnidan gnidan commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

A slice of msg.data now refers to the calldata instead of copying it to memory, so a BUG program can hold calldata bytes in a local and its debug info can say exactly which calldata bytes the local holds. For example, with a setMotd(string) call:

let offset = msg.data[4:36] as bytes32 as uint256;
let n = msg.data[4 + offset:36 + offset] as bytes32 as uint256;
let text: bytes calldata = msg.data[36 + offset:36 + offset + n];

text is now bytes calldata. Its value is one word: the offset of its first byte in the high 128 bits, and its length in the low 128 bits. So msg.data itself is the calldata size, and the IR needs no new instruction: a slice is a shl and an or, .length is an and, text[i] is a one-byte calldata read (CALLDATALOAD), and a slice of text is calldata too. A cast to bytesN loads the first 32 bytes with CALLDATALOAD and zeros the bytes past the length, as it does for memory.

The local's pointer names its word, and then the bytes:

{
  "name": "text-data",
  "location": "calldata",
  "offset": { "$quotient": [{ "$read": "text" }, "0x100000000000000000000000000000000"] },
  "length": { "$remainder": [{ "$read": "text" }, "0x100000000000000000000000000000000"] }
}

This reads exactly the right bytes when the bounds are computed at run time, as above. The pointer uses $-prefixed expressions, since #323 (~) is not merged.

The type says where the bytes are, as in Solidity. The spelling is bytes calldata: Solidity's order, and the way the compiler already prints the type. BUG has no other location or qualifier words, so there was no BUG form to follow. calldata is not reserved; it is a keyword only right after bytes in a type. msg.data and its slices are bytes calldata, so an untyped let text = msg.data[a:b] is a calldata reference as well. bytes, wherever it is written, means memory, so let text: bytes = msg.data[a:b] is a copy. bytes calldata can only be the whole type of a let or of a cast: a parameter, return type, struct field, storage variable or nested type of bytes calldata is a type error (TYPE016).

These copy bytes in calldata to memory (CALLDATACOPY, through the copy instruction from #359): a let typed bytes, an assignment to a bytes local, a cast to bytes or string, a function argument or return value, and the argument of keccak256. Assigning to a bytes or string storage variable copies, then stores the bytes through #355's storage encoding, so motd = text as string; stores the string as Solidity does. These are type errors: writing to bytes in calldata (text[0] = 1), assigning memory bytes to a bytes calldata local, and casting memory bytes to bytes calldata.

Tests that wrote into a slice of msg.data now type the local bytes, so it is a copy in memory. examples/advanced/token-registry.bug (@wip) does the same for its selector, which it compares with memory bytes.

The new test reads, stores, copies, slices, casts and bounds-checks calldata slices of 0, 5, 31, 32 and 50 bytes at levels 0 to 3. It reads and stores each one as let text, let text: bytes calldata and let text: bytes. The locals harness can now run with calldata and read calldata regions, and new soundness cases run the setMotd program above with the same three spellings. At every level, text's pointer reads its bytes at every step where it is listed: the calldata for the first two spellings, the memory copy for the third. The emitted program validates against the schema. Bytecode and debug info for the programs in examples/ that do not slice calldata are unchanged at all levels; the four that do (calldata, strings, token-registry, voting-system) change.

The BUG case study in the docs has a new section on bytes in calldata.

gnidan added a commit that referenced this pull request Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-07 03:38 UTC

A slice of msg.data, or of another slice of calldata, now refers to
the calldata instead of copying it to memory. Its value is one word:
the offset in the high 128 bits, the length in the low 128 bits. A
local's pointer reads that word and then a calldata region whose
offset and length are computed from it.

A let typed `bytes`, an assignment to memory or storage, a cast to
`bytes` or `string`, a call argument, a return value and the argument
of keccak256 copy the bytes to memory.
gnidan added a commit that referenced this pull request Oct 7, 2026
@gnidan
gnidan force-pushed the bugc-calldata-slices branch from f9146fa to a8b08cc Compare October 7, 2026 03:08
gnidan added 2 commits October 6, 2026 23:16
`let text: bytes calldata = msg.data[a:b]` is a calldata reference,
as an untyped slice of msg.data is; `let text: bytes = ...` copies to
memory. `bytes calldata` can only be the type of a let or of a cast.
@gnidan
gnidan force-pushed the bugc-calldata-slices branch from a8b08cc to 79c5fb3 Compare October 7, 2026 03:16
@gnidan
gnidan merged commit 7af7d13 into main Oct 7, 2026
7 checks passed
@gnidan
gnidan deleted the bugc-calldata-slices branch October 7, 2026 03:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant