-
Notifications
You must be signed in to change notification settings - Fork 162
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
kernel: refactor interpreter state handling #3823
kernel: refactor interpreter state handling #3823
Conversation
462e961
to
8ab6b11
Compare
8ab6b11
to
7788e50
Compare
08ef101
to
441fa08
Compare
8b4c2fe
to
22af33c
Compare
fa28f2d
to
f0f3168
Compare
ece4bd3
to
a1dd31e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some comments, will add more later.
UInt IntrReturning; | ||
UInt IntrCoding; | ||
Obj IntrState; | ||
Obj StackObj; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These variables are removed from the global GAPState and put into a stack allocated IntrState
.
WEll, all except STATE(IntrState)
which itself became superfluous.
@@ -432,14 +432,15 @@ static GVarDescriptor GVarTHREAD_EXIT; | |||
|
|||
static void ThreadedInterpreter(void * funcargs) | |||
{ | |||
IntrState intr = { 0, 0, 0, 0 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allocate IntrState
on the stack and use it below.
struct SavedReaderState s; | ||
IntrState intr = { 0, 0, 0, 0 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allocate IntrState
on the stack and use it below.
struct SavedReaderState s; | ||
IntrState intr = { 0, 0, 0, 0 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allocate IntrState
on the stack and use it below.
@@ -39,6 +39,8 @@ struct ReaderState { | |||
|
|||
ScannerState s; | |||
|
|||
IntrState intr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an instance of IntrState
into ReaderState
; it'll thus be stack-allocated as part of that in ReadEvalCommand
below
@@ -2729,35 +2730,23 @@ void ReadEvalError(void) | |||
struct SavedReaderState { | |||
UInt userHasQuit; | |||
syJmp_buf readJmpError; | |||
UInt intrCoding; | |||
UInt intrIgnoring; | |||
UInt intrReturning; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to save/restore these here anymore, as the interpreter state is on the stack -- yay!
** directly interpret, such as loops or function bodies. | ||
*/ | ||
/* TL: UInt IntrCoding; */ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above comments are all already in the header file, so they can be removed here.
// are both false. | ||
// | ||
// IgnoreLevel gives the highest value of IntrIgnoring which means this | ||
// statement is NOT ignored (this is usually, but not always, 0) | ||
static void INTERPRETER_PROFILE_HOOK(int ignoreLevel) | ||
static void INTERPRETER_PROFILE_HOOK(IntrState * intr, int ignoreLevel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The majority of the changes below either add an IntrState * intr
argument to functions; or pass intr
on to other functions that need them; or replace STATE(IntrFoo)
by intr->foo
.
From here on, a long time nothing else happens... I think for the rest of the whole file intrprtr.c
|
||
/* must be in immediate (non-ignoring, non-coding) mode */ | ||
assert( STATE(IntrIgnoring) == 0 ); | ||
assert( STATE(IntrCoding) == 0 ); | ||
GAP_ASSERT(intr->ignoring == 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, I changed a few assert
to GAP_ASSERT
since I was touching those lines anyway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a general question: Why is GAP_ASSERT
preferable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's out own macro, under our own full control; it does what we want, not the compiler or OS vendor. For now it is an alias for assert
, but only if kernel debug mode was activated by configure
/* remember old interpreter state */ | ||
if (!STATE(IntrState)) | ||
STATE(IntrState) = NEW_PLIST(T_PLIST, 16); | ||
PushPlist(STATE(IntrState), STATE(StackObj)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Real change: we don't need to push StackObj
onto a stack here and restore it later, as the interpreter state contains StackObj
and is itself allocated on the C calling stack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that we did not save IntrIgnoring, IntrCoding, IntrReturning here, but relied on the caller to do so.
a1dd31e
to
eec98fe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope to have explained all changes in this PR via comments now in one way or another.
*/ | ||
/* TL: Obj IntrState; */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As explained above, we don't need STATE(IntrState)
anymore, so this comment on its predecessor (it used to be a simple global variable) can be removed, too.
|
||
/* dummy result value (probably ignored) */ | ||
if (result) | ||
*result = 0; | ||
} | ||
|
||
// switch back to the old state | ||
STATE(StackObj) = PopPlist(STATE(IntrState)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As explained above, we don't need STATE(IntrState)
anymore.
/* remember old interpreter state */ | ||
if (!STATE(IntrState)) | ||
STATE(IntrState) = NEW_PLIST(T_PLIST, 16); | ||
PushPlist(STATE(IntrState), STATE(StackObj)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that we did not save IntrIgnoring, IntrCoding, IntrReturning here, but relied on the caller to do so.
if (intr->coding > 0) { | ||
CodeFuncCallBegin(); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I accidentally clang-formatted this if
. But that makes me wonder: since I am touching all these if (IntrCoding)
etc. checks anyway, is there any harm in running git clang-format
on them? Doesn't make the git blame
experience worse here, does it? Just reviewing would be slightly more annoying, but that could be alleviated by putting those reformatting changes into a second commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I misunderstand what you mean by
reviewing would be slightly more annoying
but I think its much easier to read that way, so I don't see why changing it in this PR would be wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add an extra commit which does the formatting, just so it will be easy to spot if looking through history/blame?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem in general reformatting is that it can make it slightly harder to see which bits of the code have changed (although in this case, it doesn't really make any difference, as you say)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think it gets slightly more annoying because the diff is bigger; and when a single line is just modified, you can see easily in the diff what was inserted/removed; while if I reformat it into multiple lines, you can't see that quite as easily; it might not even be easy to match old and new code. Anyway, it shouldn't be that bad; I'll put it into a second commit, so we can also drop it if we change our minds.
|
||
|
||
/* push 'true' (to execute body of else-branch) */ | ||
PushObj( True ); | ||
PushObj(intr, True); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another note: almost all changes which add IntrState * intr
as a parameter, resp. pass intr
as an argument, could be removed again if we turned IntrState
into a simple C++ class, and turned IntrIfElse
etc. into member functions of that.
@@ -324,49 +326,49 @@ static UInt EvalRef(const LHSRef ref, Int needExpr) | |||
return 0; | |||
} | |||
|
|||
static void AssignRef(const LHSRef ref) | |||
static void AssignRef(ReaderState * rs, const LHSRef ref) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as EvalRef
.
@@ -378,92 +380,92 @@ static void AssignRef(const LHSRef ref) | |||
} | |||
} | |||
|
|||
static void UnbindRef(ScannerState * s, const LHSRef ref) | |||
static void UnbindRef(ReaderState * rs, const LHSRef ref) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as EvalRef
; note that the ScannerState
is contained in the ReaderState
, so there is no need for us to pass both.
} | ||
} | ||
} | ||
|
||
static void IsBoundRef(ScannerState * s, const LHSRef ref) | ||
static void IsBoundRef(ReaderState * rs, const LHSRef ref) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as EvalRef
; note that the ScannerState
is contained in the ReaderState
, so there is no need for us to pass both.
@@ -713,7 +715,7 @@ static void CheckUnboundGlobal(ReaderState * rs, LHSRef ref) | |||
return; | |||
|
|||
// don't warn if we are skipping/ignoring code | |||
if (STATE(IntrIgnoring)) | |||
if (rs->intr.ignoring) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace STATE(IntrIgnoring)
} | ||
} | ||
else { | ||
Match(&rs->s, S_ASSIGN, ":=", follow); | ||
UInt currLHSGVar = rs->CurrLHSGVar; | ||
if ( LEN_PLIST(rs->StackNams) == 0 || !STATE(IntrCoding) ) { | ||
if ( LEN_PLIST(rs->StackNams) == 0 || !rs->intr.coding ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace STATE(IntrCoding)
Just as a sanity check, this doesn't effect the speed does it? I'm sure it doesn't (or if anything, would slightly speed things up, as it avoid accessing globals), but it might be worth just sanity checking. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fingolfin thanks for the comments, I think I mostly understand what this PR changes, I added some questions but nothing that blocks merging. I was also looking at #3819 but I wasn't fast enough...
ExecStatus returning; | ||
|
||
// 'StackObj' is the stack of values. | ||
Obj StackObj; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be a silly question, but since based on the comments it only matters whether the first three variables are non-zero or not, why aren't they bool
or BOOL
? What are the remaining 63 bits needed for?
|
||
void IntrLe(void); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General C question: What is an argument void
? Is that the same as IntrLe()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, (void) means no arguments. In C++, both (void) and () mean the same thing.
In C, () doesn't mean no arguments! It means "unknown number and types of arguments". However, no-one should ever write this, and most people assume () means "no arguments". To see this, gcc will compile this without complaint:
void f() {}
int main(void)
{ f(2,3,4); }
(This is actually a slight simplification of what () means, where I don't think it's worth learning the full horribleness, as it's not actually useful :) )
if (intr->coding > 0) { | ||
CodeFuncCallBegin(); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I misunderstand what you mean by
reviewing would be slightly more annoying
but I think its much easier to read that way, so I don't see why changing it in this PR would be wrong.
|
||
/* must be in immediate (non-ignoring, non-coding) mode */ | ||
assert( STATE(IntrIgnoring) == 0 ); | ||
assert( STATE(IntrCoding) == 0 ); | ||
GAP_ASSERT(intr->ignoring == 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a general question: Why is GAP_ASSERT
preferable?
@ChrisJefferson I measured |
I now also went to a Linux server (less fluctuation than on my laptop), and used perf tool to measure GAP's startup time (since the whole library has to be read, it's quite sensitive to changes in the reader and interpreter. I measures the head commits of the three stable branches for 4.9, 4.10 and 4.11, as well as this PR (the GASMAN fix from PR #3840 does not seem to have an effect). The file stable-4.9
stable-4.10
stable-4.11
This PR
What we can see is that this PR has no direct impact, but we do see a regression of the startup time, it got slower with each release :-(. One suspect for this I have in mind are th changes to decouple scanner and IO code; I'll investigate if those indeed are (part of) the cause. If they are, I already was planning to work on that for quite some time, it just wasn't high on my priority list. |
Thanks, that's more than enough testing! |
This continues refactoring work begun in PRs #3805 and #3819.
Moving the interpreter state into a dedicated struct which is allocated on the C stack has multiple advantages; the latter commits in this PR exploit several of them:
ExecBegin
/ExecEnd
IntrBegin
/IntrEnd
IntrBegin
/IntrEnd
from 5 down to 2, both inread.c
and in places where they clearly belongUPDATE: I've removed all but one commit from this PR, to keep it simple. I'll add the other back in a separate PR (or more) once this PR here has been merged.