Skip to content
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

Fix Errors in Generator Jit for SlotArray and CopyProp Restores #6700

Merged
merged 2 commits into from
Apr 18, 2021

Conversation

rhuanjl
Copy link
Collaborator

@rhuanjl rhuanjl commented Apr 14, 2021

Issue 1 - Slot Array

The SlotArray is used to store variables that belong to local scopes (let and const), on an InterpreterStackFrame this is stored in a different location to other registers.

When resuming a jitted generator/async function after a yield/await point these local scopes must be restored BUT there is no logic to handle this restoration.

Instead immediately before the function is resumed in CallGenerator() copy the local scopes into "normal" reg slots that the jit can read from.

Notes:

  1. this is only necessary if the function has already been partially executed (hence only do it if there is an Interpreter Stack Frame)
  2. all Async functions, generator functions and AsyncGenerator functions go through CallGenerator - hence this is the right place for the fix
  3. The jit is already trying to load these values from the location I'm now moving them to, see logic here:
    else
    {
    srcOpnd = IR::IndirOpnd::New(
    this->interpreterFrameRegOpnd,
    this->GetOffsetFromInterpreterStackFrame(regSlot),
    copyPropStackSym->GetType(),
    this->func
    );
    }

    AND here:
    https://github.com/chakra-core/ChakraCore/blob/master/lib/Backend/LinearScan.cpp#L5371
    BUT was finding nothing - if the function begun in the interpreter it doesn't to write to this location, if it begun in the jit it's 0'd out when the function yields/awaits here:
    uint32 innerScopeCount = executeFunction->GetInnerScopeCount();
    for (uint32 i = 0; i < innerScopeCount; i++)
    {
    Js::RegSlot reg = executeFunction->GetFirstInnerScopeRegister() + i;
    newInstance->SetInnerScopeFromIndex(i, newInstance->GetNonVarReg(reg));
    newInstance->SetNonVarReg(reg, nullptr);
    }

Fix: #6678
Fix: #6679
Fix: #6692
Fix: #6690
Fix: #6697
Fix: #6698
Fix: #6699
Fix: #6705

Issue 2 - Copy prop'd syms

A book keeping error was resulting in hitting a FailFast unnecessarily when setting up a restore for a copy prop'd sym after a yield.

Also, whether or not a copy prop sym should be restored was being checked using its Key; correct if restoring to Key BUT when restoring to Value need to check based on Value.

Note, I've added the full POC from issue 6686 as a new test file because reducing it proved difficult.

Fix: #6685
Fix: #6686

@bin2415 thanks for the report
@xuelanxu thanks for the report

@rhuanjl
Copy link
Collaborator Author

rhuanjl commented Apr 14, 2021

@nhat-nguyen this was an interesting bug - curious if you’d come across it at all?

@@ -1792,7 +1792,7 @@ void ByteCodeGenerator::FinalizeRegisters(FuncInfo* funcInfo, Js::FunctionBody*
}
}

// NOTE: The FB expects the yield reg to be the final non-temp.
// NOTE: The FunctionBody expects the yield reg to be the final non-temp.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ride-along comment improvement as I was tracking through where different registers go it took me a while to work out what an FB was

Comment on lines +179 to +184
uint32 innerScopeCount = this->scriptFunction->GetFunctionBody()->GetInnerScopeCount();
for (uint32 i = 0; i < innerScopeCount; ++i)
{
Js::RegSlot reg = this->scriptFunction->GetFunctionBody()->GetFirstInnerScopeRegister() + i;
this->frame->SetNonVarReg(reg, this->frame->InnerScopeFromIndex(i));
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is effectively an inverse of lines 1742-1748 of BailOut.cpp.

NOTE - getting rid of the logic in BailOut.cpp would not be a sufficient fix as it's possible for a generator to start in the interpreter and transition to the Jit at a yield point

@@ -90,4 +90,51 @@ check(gen4.next().value, 1);
check(gen4.next().value, 2);
check(gen4.next().value, 3);

title("Load Scope Slots in presence of for-in");
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Note all these cases could be done as async functions (swap yield to await) - testing only as generators as internally the important code pathways are identical and doing it with generators means the test case doesn't have to wait for promises to resolve.

@@ -5135,8 +5135,10 @@ void LinearScan::GeneratorBailIn::BuildBailInSymbolList(

if (unrestorableSymbols.TestAndClear(value->m_id))
{
if (this->NeedsReloadingSymWhenBailingIn(copyPropSym.Key()))
if (this->NeedsReloadingSymWhenBailingIn(copyPropSym.Value()))
Copy link
Collaborator Author

@rhuanjl rhuanjl Apr 14, 2021

Choose a reason for hiding this comment

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

Compare with lines 5146-5150.

Important point is we must check if this Sym needs reloading based on its BackEndId (see line 5140 compared with line 5148.

@rhuanjl rhuanjl changed the title Handle SlotArray loads in jitted generators Fix Errors in Generator Jit for SlotArray and CopyProp Restores Apr 14, 2021
{
BailInSymbol bailInSym(key->m_id /* fromByteCodeRegSlot */, value->m_id /* toBackendId */);
bailInSymbols->PrependNode(this->func->m_alloc, bailInSym);
}
}
else if (unrestorableSymbols.TestAndClear(key->m_id))
if (unrestorableSymbols.TestAndClear(key->m_id))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sometimes both the 'value' and 'key' appear in the unrestorableSymbols list; therefore always check both.

- copy prop'd syms can be referenced by key and value
- both key and value can be in the unrestorableSymbols list
- always check for both key and value
- in each case check if the symbol is needed based on the BackEndId being checked 'key' or 'value'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment