Constant operands like `i32.const -1` are stored as signed LEB128, and the sign has to be extended correctly or negative numbers come out huge and positive. Today you decode the signed form and pin the sign-extension boundary.
Decode a signed LEB128 integer, sign-extending the result so negative values decode correctly.
Signed LEB128 uses the same 7-bits-per-byte, continuation-flag loop as the unsigned form, but the value is two’s complement, so the final byte carries a sign. Bit 6 of the last byte (the mask 0x40, the top bit of that byte’s seven payload bits) is the sign bit. If it is set, the number is negative and every higher bit is an implied 1 - so after the loop you must sign-extend: fill the bits above what you decoded with ones. Skip that step and 0x7F, which should be -1, comes out as 127.
The boundary to feel is why 64 needs two bytes while -64 needs only one. A lone 0x40 has bit 6 set, so it decodes as the negative number -64; to encode positive 64 you must add a second byte (0xC0 0x00) so the sign bit lands on a clear bit. This same off-by-one sign boundary is exactly where a naive port to a language without a fixed-width int32 goes wrong, so it is worth pinning now with concrete values.
// Same 7-bits-per-byte loop, but after the final byte, if its sign bit// (bit 6, value 0x40) is set and there is room, extend the sign upward.func (c *Cursor) readVarS32() (int32, error) {var result int32var shift uintvar b bytefor {// read b, OR in (b & 0x7F) << shift, shift += 7// break when b & 0x80 == 0}if shift < 32 && b&0x40 != 0 {result |= -1 << shift // sign-extend}return result, nil}