public byte[] read(long offset, int size, Reason reason) {
		ULongSpanSet uninitialized = computeUninitialized(offset, size);
		if (uninitialized.isEmpty()) {
			return readBytes(offset, size, reason);
		}
		if (backing != null) {
			uninitialized = readUninitializedFromBacking(uninitialized);
			if (uninitialized.isEmpty()) {
				return readBytes(offset, size, reason);
			}
		}

		/**
		 * The decoder will buffer ahead, so give it as much as we can, but no more than is actually
		 * initialized. If it's a (non-decode) read, give it everything, but invoke the warning.
		 */
		if (reason == Reason.EXECUTE_DECODE) {
			Iterator<ULongSpan> it =
				uninitialized.complement(ULongSpan.extent(offset, size)).iterator();
			if (it.hasNext()) {
				ULongSpan init = it.next();
				if (init.min().longValue() == offset) {
					return readBytes(offset, (int) init.length(), reason);
				}
			}
		}

		if (reason == Reason.EXECUTE_READ) {
			warnUninit(uninitialized);
		}
		else if (reason == Reason.EXECUTE_DECODE) {
			/**
			 * The callers may be reading ahead, so it's not appropriate to throw an exception here.
			 * Instead, communicate there's no more. If the buffer's empty on their end, they'll
			 * handle the error as appropriate. If it's in the emulator, the instruction decoder
			 * should eventually throw the decode exception.
			 */
			return EMPTY;
		}
		return readBytes(offset, size, reason);
	}
Read a value from the space at the given offset

<p>
If this space is not acting as a cache, this simply delegates to
{@link #readBytes(long, int, Reason)}. Otherwise, it will first ensure the cache covers the
requested value.

@param offset the offset
@param size the number of bytes to read (the size of the value)
@param reason the reason for reading state
@return the bytes read
Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java