The 64-bit integer type is the i32 family at double the width. Today you add i64 constants and arithmetic, reusing your value slot and pinning the wrap at the 64-bit boundary.
Execute i64.const and the i64 add, subtract, and multiply instructions, wrapping at 64 bits.
Because you built the value slot to hold 64 bits from the start, i64 needs no new storage - only new opcodes that read and write the full width instead of the low half. i64.const (0x42) decodes a signed LEB128, exactly like i32.const but allowed to run up to 64 bits of value, and i64.add, i64.sub, i64.mul are the same pop-two-push-one arithmetic you wrote for i32, just at the wider type. This is the payoff of picking a general representation early: widening the integer type is additive, not a refactor.
The boundary to pin is the wider wrap. Where i32 overflows past 2^31, i64 overflows past 2^63, so 0x7FFFFFFFFFFFFFFF + 1 wraps from the largest positive value to the smallest negative one, -9223372036854775808. The same modulo-2^width two’s-complement rule you learned for i32 applies here at double the width. If your language has a native 64-bit integer this is automatic; if it does not, the same masking discipline extends to 64 bits. The i64 division, comparison, and bit operations follow the identical i32 pattern and are completed in the finalize pass.
// Your value slot already holds 64 bits, so i64 needs no new storage - just// new opcodes that read and write the full width. i64.const is 0x42.case 0x42: // i64.constv, err := readVarS64(body, &pc) // signed LEB128, up to 64 bitsstack.Push(I64(v))// 0x7C i64.add, 0x7D i64.sub, 0x7E i64.mul