New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a new stack and error handling model for the SourcePawn VM. #285

Merged
merged 1 commit into from Mar 5, 2015

Conversation

Projects
None yet
1 participant
@dvander
Member

dvander commented Mar 1, 2015

Most of the goodies about this change are in the commit message, so I'll do a different overview here. This patch continues to whittle away at the concept of a "context", this time by presenting a unified stack and error handling model in the API. Stack traces now follow all functions on the stack, including natives, and other plugins. It is now possible to grab a full stack trace at pretty much any point in time.

This unification also changes how errors are propagated. SourcePawn now internally throws exceptions (an error code and a message), which can be handled in C++. For compatibility, IPluginFunction::Execute swallows all exceptions, but still returns an error code. The newer variant, Invoke(), will propagate exceptions. An API is provided for creating an exception handler in C++. (They do not exist in the language yet.)

For the most part, very little needs to change for Core developers or extension authors. Callers that care about the return value of IPluginFunction::Execute() should simply consider using Invoke and an exception handler instead, and callers of GetLastNativeError should use an exception handler. DetectExceptions is provided as a default handler that simply rethrows the exception. Using exception handlers does come with a caveat: if an exception is rethrown, the code must return to caller immediately. But this has always been the case for SourcePawn: error codes must be propagated.

The last piece of note is that as part of this unification, the RP stack has been removed. Rather than maintain complex data structures while code is running, the new stack frame iterator has been taught to decode the layout of the JIT stack. This should be a significant performance improvement for tight loops, especially ones that make function calls.

The first benchmark I tried performs an empty call:

void empty() {}
public int main() {
  for (int i = 0; i < 500000000; i++)
    empty();
}

The second benchmark performs an empty native call:

native void donothing();
public int main() {
  for (int i = 0; i < 500000000; i++)
    donothing();
}

The results on my Core-i5 laptop:

               Before     After      Change
empty-native   4.108s     3.405s   17% better
empty-call     2.918s     1.717s   41% better

I'll take it.

Implement a new stack and error handling model for the SourcePawn VM.
This has three major changes to SourcePawn. First, the API now supports the concept of "exceptions". The exception state is a global property of an instance of the SourcePawn VM. Exceptions can be caught or suppressed. Many places in SourceMod have been updated to check exceptions instead of errors.

The new API obsoletes major parts of the embedder API - all but one method of invoking functions is obsoleted, and the debug interface has been scrapped. Extensions using the native API will not be affected, however, ThrowNativeError has been deprecated in favor of ReportError.

Second, the SourcePawn concept of a "stack" has been unified at the API level. A stack frame iterator now iterates over all SourcePawn invocations, rather than the topmost plugin. This makes error handling more consistent and removes another dependency on context-per-plugin.

Finally, the implementation of stack frames has been changed dramatically. Rather than maintain a complicated and expensive return pointer stack, we now rely on the implicit one provided by the CPU. The stack frame iterator now walks the JIT stack directly. This removes many unnecessary bookkeeping instructions from the generated code, in particular making the CALL instruction 40% faster.

These changes required some fair surgery to the JIT. Its error paths are now slightly more complicated, as they have to throw an exception rather than return an error code. In addition, any path that can throw an exception is now responsible for creating an "exit frame", which exists to tell the stack frame iterator about transitions from the JIT to the VM.

dvander added a commit that referenced this pull request Mar 5, 2015

Merge pull request #285 from alliedmodders/frames
Implement a new stack and error handling model for the SourcePawn VM.

@dvander dvander merged commit 715a51d into master Mar 5, 2015

1 check was pending

continuous-integration/travis-ci/pr The Travis CI build is in progress
Details

@dvander dvander deleted the frames branch Mar 5, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment