Skip to content

Span-based bulk peeks (the boring kind)#3472

Draft
YoshiRulz wants to merge 4 commits intomasterfrom
bulk-peek-noalloc
Draft

Span-based bulk peeks (the boring kind)#3472
YoshiRulz wants to merge 4 commits intomasterfrom
bulk-peek-noalloc

Conversation

@YoshiRulz
Copy link
Member

@YoshiRulz YoshiRulz commented Dec 4, 2022

This isn't using ReadOnlySpan to let the frontend and APIs read memory without copying. This still copies, but into a Span instead of an array, which probably makes no difference for internal use, and is slightly more convenient for ext. tools.
I could have exposed the overload which takes an array reference and writes into it, but I figured this way would mitigate user error. The use of an unsigned integer for the start offset, especially.

I'd appreciate if someone could double-check my pointer arithmetic is correct, i.e. it matches the arithmetic in the other overload's implementation.
edit: Rebased, would still like that second opinion, and I think this should wait for #3965.

@YoshiRulz YoshiRulz force-pushed the bulk-peek-noalloc branch from f78d7da to 531aebb Compare June 1, 2025 00:50
@YoshiRulz YoshiRulz mentioned this pull request Nov 29, 2025
2 tasks
LogCallback($"Warning: attempted read of {srcStartOffset} outside the memory size of {d.Size}");
return false;
}
try
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the new method have a try block?
Lua already puts everything inside a try block, and I think that is better: an exception should stop the Lua script. For C# users, I think it better to let them decide what to do.

(IMO attempting to read/write outside the range of the memory domain should also throw but that's a separate change)

/// If this happens, the state of <paramref name="dstBuffer"/> is undefined. Anywhere from <c>0</c> bytes to all of them could have been copied.
/// The exception is when <paramref name="srcStartOffset"/> falls outside the domain, as that is checked first and results in a no-op.
/// </returns>
bool ReadByteRange(ulong srcStartOffset, Span<byte> dstBuffer, string domain = null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment here is wrong. It returns false if the internal API call threw an exception, but not iff: the method itself contains a check for when the start falls outside the domain.
This is also different behavior from the other overload, which will still return some bytes just starting at the beginning of the memory domain. I think they should have the same behavior. (which again IMO should be throwing)


if ((start & 1) != 0 || (end & 1) != 0)
throw new InvalidOperationException("The API contract doesn't define what to do for unaligned reads and writes!");
if (start % 2 is not 0 || end % 2 is not 0) throw new InvalidOperationException(ERR_MSG_NOT_ALIGNED);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you changing this? (start & 1) != 0 is easier to understand, and far nicer than start % 2 is not 0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah & is a standard way to check individual bits.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't querying a bitfield though, it's checking address alignment. Later in the file is a % 4. Let the compiler optimise it to a mask.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Address alignment is inherently tied to bits, which makes the bitwise operator more appropriate.
I also much prefer the standard equality operator == over is and especially prefer != over is not. (is would make sense with pattern matching, maybe even checking for null, but not for plain equality checks. And having whitespace inside the is not "operator" is just terrible imo.)
But even disregarding all that, this is a style change that isn't relevant to this PR.

if (start % 2 is not 0 || end % 2 is not 0) throw new InvalidOperationException(ERR_MSG_NOT_ALIGNED);

if (values.LongLength * 2 != end - start)
if (checked((ulong) values.Length * 2UL) != addresses.Count())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checked here does nothing. Unless Length can somehow be negative, this expression can never overflow.

}
}

public virtual byte[] BulkPeekByte(Range<long> addresses)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be a good idea to make void BulkPeekByte(Range<long> addresses, byte[] values) protected, and change any call sites to use this (or the span version)?

@YoshiRulz YoshiRulz marked this pull request as draft December 3, 2025 23:25
@YoshiRulz YoshiRulz mentioned this pull request Feb 22, 2026
2 tasks
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.

4 participants