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 and Enable Jitting Generators #6264

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/Backend/BailOut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1774,6 +1774,12 @@ BailOutRecord::BailOutHelper(Js::JavascriptCallStackLayout * layout, Js::ScriptF

BAILOUT_FLUSH(executeFunction);

if (executeFunction->IsCoroutine() && bailOutKind != IR::BailOutForGeneratorYield)
{
Js::ResumeYieldData* resumeYieldData = static_cast<Js::ResumeYieldData*>(args[1]);
newInstance->SetNonVarReg(executeFunction->GetYieldRegister(), resumeYieldData);
}

executeFunction->BeginExecution();

// Restart at the bailout byte code offset.
Expand Down
27 changes: 23 additions & 4 deletions lib/Backend/IRBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1768,7 +1768,10 @@ IRBuilder::BuildReg2(Js::OpCode newOpcode, uint32 offset)
void
IRBuilder::BuildReg2(Js::OpCode newOpcode, uint32 offset, Js::RegSlot R0, Js::RegSlot R1, uint32 nextOffset)
{
IR::RegOpnd * src1Opnd = this->BuildSrcOpnd(R1);
// special case required for AsyncYieldIsReturn where the source will be the ResumeYieldData
// which will be loaded via a backend only instruction inserted below
IR::RegOpnd * src1Opnd = newOpcode != Js::OpCode::AsyncYieldIsReturn ?
this->BuildSrcOpnd(R1): this->BuildDstOpnd(R1);
StackSym * symSrc1 = src1Opnd->m_sym;
bool reuseLoc = false;

Expand Down Expand Up @@ -1897,6 +1900,19 @@ IRBuilder::BuildReg2(Js::OpCode newOpcode, uint32 offset, Js::RegSlot R0, Js::Re
return;
}

case Js::OpCode::AsyncYieldIsReturn:
{
// Similar to the above this OpCode also relies on the ResumeYieldData passed in as an argument to the jit'd frame
// However this OpCode expects it to be the source and then has a destination
IR::Instr* loadResumeYieldData = IR::Instr::New(Js::OpCode::GeneratorLoadResumeYieldData, src1Opnd, m_func);
this->AddInstr(loadResumeYieldData, offset);

instr = IR::Instr::New(newOpcode, dstOpnd /* dst */, src1Opnd /* src1 */, m_func);
this->AddInstr(instr, offset);

return;
}

case Js::OpCode::Yield:
instr = IR::Instr::New(newOpcode, dstOpnd, src1Opnd, m_func);
this->AddInstr(instr, offset);
Expand Down Expand Up @@ -7801,9 +7817,6 @@ IRBuilder::GeneratorJumpTable::BuildJumpTable()
IR::Instr* createInterpreterFrame = IR::Instr::New(Js::OpCode::GeneratorCreateInterpreterStackFrame, genFrameOpnd /* dst */, genRegOpnd /* src */, this->m_func);
this->m_irBuilder->AddInstr(createInterpreterFrame, this->m_irBuilder->m_functionStartOffset);

IR::BranchInstr* skipJumpTable = IR::BranchInstr::New(Js::OpCode::Br, functionBegin, this->m_func);
this->m_irBuilder->AddInstr(skipJumpTable, this->m_irBuilder->m_functionStartOffset);

// Label to insert any initialization code
// $initializationCode:
this->m_irBuilder->AddInstr(initCode, this->m_irBuilder->m_functionStartOffset);
Expand Down Expand Up @@ -7838,6 +7851,12 @@ IRBuilder::GeneratorJumpTable::BuildJumpTable()
instr->SetSrc1(curOffsetOpnd);
this->m_irBuilder->AddInstr(instr, this->m_irBuilder->m_functionStartOffset);

// TODO: Improvements
// If the code falls through to here, it means that none of the offsets matched. This must be the first
// time we create the interpreter frame. Skip this bailout.
IR::BranchInstr* skipBailOutForElidedYield = IR::BranchInstr::New(Js::OpCode::Br, functionBegin, this->m_func);
this->m_irBuilder->AddInstr(skipBailOutForElidedYield, this->m_irBuilder->m_functionStartOffset);

this->m_func->m_bailOutForElidedYieldInsertionPoint = instr;

this->m_irBuilder->AddInstr(functionBegin, this->m_irBuilder->m_functionStartOffset);
Expand Down
34 changes: 29 additions & 5 deletions lib/Backend/Lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29252,11 +29252,35 @@ Lowerer::LowerGeneratorHelper::LowerGeneratorLoadResumeYieldData(IR::Instr* inst
void
Lowerer::LowerGeneratorHelper::LowerResumeGenerator(IR::Instr* instr)
{
IR::Opnd* srcOpnd1 = instr->UnlinkSrc1();
IR::Opnd* srcOpnd2 = instr->m_opcode == Js::OpCode::ResumeYieldStar ? instr->UnlinkSrc2() : IR::AddrOpnd::NewNull(this->func);
this->lowererMD.LoadHelperArgument(instr, srcOpnd2);
this->lowererMD.LoadHelperArgument(instr, srcOpnd1);
this->lowererMD.ChangeToHelperCall(instr, IR::HelperResumeYield);
if (instr->m_opcode == Js::OpCode::ResumeYieldStar)
{
IR::Opnd* srcOpnd1 = instr->UnlinkSrc1();
IR::Opnd* srcOpnd2 = instr->UnlinkSrc2();
this->lowererMD.LoadHelperArgument(instr, srcOpnd2);
this->lowererMD.LoadHelperArgument(instr, srcOpnd1);
this->lowererMD.ChangeToHelperCall(instr, IR::HelperResumeYield);
}
else
{
Assert(instr->GetSrc1()->IsRegOpnd());
IR::RegOpnd* resumeYieldData = instr->UnlinkSrc1()->AsRegOpnd();
IR::IndirOpnd* exceptionObj = IR::IndirOpnd::New(resumeYieldData, Js::ResumeYieldData::GetOffsetOfExceptionObject(), TyMachPtr, this->func);
IR::IndirOpnd* resumeData = IR::IndirOpnd::New(resumeYieldData, Js::ResumeYieldData::GetOffsetOfData(), TyMachPtr, this->func);

IR::LabelInstr* helper = IR::LabelInstr::New(Js::OpCode::Label, this->func);
IR::LabelInstr* done = IR::LabelInstr::New(Js::OpCode::Label, this->func);

this->lowerer->InsertCompareBranch(exceptionObj, IR::AddrOpnd::NewNull(this->func), Js::OpCode::BrNotAddr_A, helper, instr);
this->lowerer->InsertMove(instr->UnlinkDst(), resumeData, instr);
this->lowerer->InsertBranch(Js::OpCode::Br, done, instr);
instr->InsertBefore(helper);

this->lowererMD.LoadHelperArgument(instr, IR::AddrOpnd::NewNull(this->func));
this->lowererMD.LoadHelperArgument(instr, resumeYieldData);
this->lowererMD.ChangeToHelperCall(instr, IR::HelperResumeYield);

instr->InsertAfter(done);
}
}

void
Expand Down
9 changes: 8 additions & 1 deletion lib/Common/ConfigFlagsList.h
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,13 @@ PHASE(All)
#define DEFAULT_CONFIG_ES6Destructuring (true)
#define DEFAULT_CONFIG_ES6ForLoopSemantics (true)
#define DEFAULT_CONFIG_ES6FunctionNameFull (true)

#ifdef _M_X64
// Jitting Generator functions and async functions - currently only works on x64
#define DEFAULT_CONFIG_JitES6Generators (true)
#else
#define DEFAULT_CONFIG_JitES6Generators (false)
#endif
#define DEFAULT_CONFIG_ES6Generators (true)
#define DEFAULT_CONFIG_ES6IsConcatSpreadable (true)
#define DEFAULT_CONFIG_ES6Math (true)
Expand Down Expand Up @@ -1226,7 +1233,7 @@ FLAGR(Boolean, ESImportMeta, "Enable import.meta keyword", DEFAULT_CONFIG_ESImpo
FLAGR(Boolean, ESGlobalThis, "Enable globalThis", DEFAULT_CONFIG_ESGlobalThis)

// This flag to be removed once JITing generator functions is stable
FLAGNR(Boolean, JitES6Generators , "Enable JITing of ES6 generators", false)
FLAGNR(Boolean, JitES6Generators , "Enable JITing of ES6 generators", DEFAULT_CONFIG_JitES6Generators)

FLAGNR(Boolean, FastLineColumnCalculation, "Enable fast calculation of line/column numbers from the source.", DEFAULT_CONFIG_FastLineColumnCalculation)
FLAGR (String, Filename , "Jscript source file", nullptr)
Expand Down
19 changes: 17 additions & 2 deletions lib/Runtime/Base/FunctionBody.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,28 @@ namespace Js
bool
FunctionBody::SkipAutoProfileForCoroutine() const
{
return this->IsCoroutine() && CONFIG_ISENABLED(Js::JitES6GeneratorsFlag);
return this->IsCoroutine() && CONFIG_FLAG(JitES6Generators);
rhuanjl marked this conversation as resolved.
Show resolved Hide resolved
}

bool
FunctionBody::IsGeneratorAndJitIsDisabled() const
{
return this->IsCoroutine() && !(CONFIG_ISENABLED(Js::JitES6GeneratorsFlag) && !this->GetHasTry() && !this->IsInDebugMode() && !this->IsAsync());
if (!this->IsCoroutine())
{
return false;
}
if (!CONFIG_FLAG(JitES6Generators) || this->GetHasTry() || this->IsInDebugMode() || (this->IsAsync() && this->IsGenerator()))
{
return true;
}
else
{
#if ENABLE_TTD
return this->GetScriptContext()->GetThreadContext()->IsRuntimeInTTDMode() && this->IsAsync();
#else
return false;
#endif
}
}

ScriptContext* EntryPointInfo::GetScriptContext()
Expand Down
7 changes: 6 additions & 1 deletion lib/Runtime/Base/FunctionInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ namespace Js
Method = 0x400000, // The function is a method
ComputedName = 0x800000,
ActiveScript = 0x1000000,
HomeObj = 0x2000000
HomeObj = 0x2000000,
EvaluateNonSimpleParameterListForGenerator = 0x8000000
};
FunctionInfo(JavascriptMethod entryPoint, Attributes attributes = None, LocalFunctionId functionId = Js::Constants::NoFunctionId, FunctionProxy* functionBodyImpl = nullptr);
FunctionInfo(JavascriptMethod entryPoint, _no_write_barrier_tag, Attributes attributes = None, LocalFunctionId functionId = Js::Constants::NoFunctionId, FunctionProxy* functionBodyImpl = nullptr);
Expand Down Expand Up @@ -83,6 +84,10 @@ namespace Js
static bool HasHomeObj(Attributes attributes) { return (attributes & Attributes::HomeObj) != 0; }
bool HasHomeObj() const { return HasHomeObj(this->attributes); }

bool ShouldEvaluateNonSimpleParameterListForGenerator() const {
return (attributes & Attributes::EvaluateNonSimpleParameterListForGenerator) != 0;
}

BOOL HasBody() const { return functionBodyImpl != NULL; }
BOOL HasParseableInfo() const { return this->HasBody() && !this->IsDeferredDeserializeFunction(); }

Expand Down
9 changes: 6 additions & 3 deletions lib/Runtime/ByteCode/ByteCodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3060,9 +3060,12 @@ void ByteCodeGenerator::EmitOneFunction(ParseNodeFnc *pnodeFnc)

// If the function has non simple parameter list, the params needs to be evaluated when the generator object is created
// (that is when the function is called). This yield opcode is to mark the begining of the function body.
// TODO: Inserting a yield should have almost no impact on perf as it is a direct return from the function. But this needs
// to be verified. Ideally if the function has simple parameter list then we can avoid inserting the opcode and the additional call.
if (pnodeFnc->IsGenerator() && !pnodeFnc->IsModule())
// this is avoided when not required
if (pnodeFnc->IsGenerator() && !pnodeFnc->IsModule() && (
#if ENABLE_TTD
this->GetScriptContext()->GetThreadContext()->IsRuntimeInTTDMode() ||
#endif
pnodeFnc->HasNonSimpleParameterList()))
{
EmitDummyYield(this, funcInfo);
}
Expand Down
5 changes: 5 additions & 0 deletions lib/Runtime/ByteCode/ByteCodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,7 @@ static const Js::FunctionInfo::Attributes StableFunctionInfoAttributesMask = (Js
Js::FunctionInfo::Attributes::ClassMethod |
Js::FunctionInfo::Attributes::Method |
Js::FunctionInfo::Attributes::Generator |
Js::FunctionInfo::Attributes::EvaluateNonSimpleParameterListForGenerator |
Js::FunctionInfo::Attributes::Module |
Js::FunctionInfo::Attributes::ComputedName |
Js::FunctionInfo::Attributes::HomeObj
Expand Down Expand Up @@ -1293,6 +1294,10 @@ static Js::FunctionInfo::Attributes GetFunctionInfoAttributes(ParseNodeFnc * pno
if (pnodeFnc->IsGenerator())
{
attributes = (Js::FunctionInfo::Attributes)(attributes | Js::FunctionInfo::Attributes::Generator);
if (pnodeFnc->HasNonSimpleParameterList())
{
attributes = (Js::FunctionInfo::Attributes)(attributes | Js::FunctionInfo::Attributes::EvaluateNonSimpleParameterListForGenerator);
}
}
if (pnodeFnc->IsAccessor())
{
Expand Down
11 changes: 11 additions & 0 deletions lib/Runtime/Language/InterpreterStackFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,17 @@ namespace Js
else
{
newInstance = CreateInterpreterStackFrameForGenerator(function, executeFunction, generator, doProfile);
if (generator->GetIsAsync())
{
ResumeYieldData* resumeYieldData = static_cast<ResumeYieldData*>(args[1]);
newInstance->SetNonVarReg(executeFunction->GetYieldRegister(), resumeYieldData);

// The debugger relies on comparing stack addresses of frames to decide when a step_out is complete so
// give the InterpreterStackFrame a legit enough stack address to make this comparison work.
newInstance->m_stackAddress = reinterpret_cast<DWORD_PTR>(&generator);

newInstance->retOffset = 0;
}
}
}
else
Expand Down
19 changes: 16 additions & 3 deletions lib/Runtime/Library/JavascriptGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -602,11 +602,24 @@ namespace Js
ResumeYieldData data(scriptContext->GetLibrary()->GetUndefined(), nullptr);
Var thunkArgs[] = { this, &data };
Arguments arguments(_countof(thunkArgs), thunkArgs);
BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())

FunctionInfo* funcInfo = this->scriptFunction->GetFunctionInfo();

if (
#if ENABLE_TTD
scriptContext->GetThreadContext()->IsRuntimeInTTDMode() ||
#endif
funcInfo->IsModule() ||
funcInfo->ShouldEvaluateNonSimpleParameterListForGenerator()
)
{
JavascriptFunction::CallFunction<1>(this->scriptFunction, this->scriptFunction->GetEntryPoint(), arguments);
BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())
{
JavascriptFunction::CallFunction<1>(this->scriptFunction, this->scriptFunction->GetEntryPoint(), arguments);
}
END_SAFE_REENTRANT_CALL
}
END_SAFE_REENTRANT_CALL

SetState(JavascriptGenerator::GeneratorState::SuspendedStart);
}

Expand Down
4 changes: 4 additions & 0 deletions lib/Runtime/Library/JavascriptGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ namespace Js

ResumeYieldData(Var data, JavascriptExceptionObject* exceptionObj, JavascriptGenerator* generator = nullptr) :
data(data), exceptionObj(exceptionObj), generator(generator) {}

static uint32 GetOffsetOfData() { return offsetof(ResumeYieldData, data); }

static uint32 GetOffsetOfExceptionObject() { return offsetof(ResumeYieldData, exceptionObj); }
};

struct AsyncGeneratorRequest
Expand Down
26 changes: 16 additions & 10 deletions lib/Runtime/Library/JavascriptGeneratorFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,25 @@ using namespace Js;
CopyArray(argsHeapCopy, stackArgs.Info.Count, stackArgs.Values, stackArgs.Info.Count);
Arguments heapArgs(callInfo, unsafe_write_barrier_cast<Var*>(argsHeapCopy));

DynamicObject* prototype = scriptContext->GetLibrary()->CreateGeneratorConstructorPrototypeObject();
DynamicObject* prototype = VarTo<DynamicObject>(JavascriptOperators::GetPropertyNoCache(function, Js::PropertyIds::prototype, scriptContext));
JavascriptGenerator* generator = scriptContext->GetLibrary()->CreateGenerator(heapArgs, generatorFunction->scriptFunction, prototype);
// Set the prototype from constructor
JavascriptOperators::OrdinaryCreateFromConstructor(function, generator, prototype, scriptContext);
FunctionInfo* funcInfo = generatorFunction->scriptFunction->GetFunctionInfo();

// Call a next on the generator to execute till the beginning of the body
BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())
if (
#if ENABLE_TTD
scriptContext->GetThreadContext()->IsRuntimeInTTDMode() ||
#endif
funcInfo->IsModule() ||
funcInfo->ShouldEvaluateNonSimpleParameterListForGenerator()
)
{
CALL_ENTRYPOINT(scriptContext->GetThreadContext(), generator->EntryNext, function, CallInfo(CallFlags_Value, 1), generator);
// Call a next on the generator to execute till the beginning of the body
BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())
{
CALL_ENTRYPOINT(scriptContext->GetThreadContext(), generator->EntryNext, function, CallInfo(CallFlags_Value, 1), generator);
}
END_SAFE_REENTRANT_CALL
}
END_SAFE_REENTRANT_CALL

return generator;
}
Expand All @@ -197,12 +205,10 @@ using namespace Js;
CopyArray(argsHeapCopy, stackArgs.Info.Count, stackArgs.Values, stackArgs.Info.Count);
Arguments heapArgs(callInfo, unsafe_write_barrier_cast<Var*>(argsHeapCopy));

DynamicObject* prototype = scriptContext->GetLibrary()->CreateAsyncGeneratorConstructorPrototypeObject();
DynamicObject* prototype = VarTo<DynamicObject>(JavascriptOperators::GetPropertyNoCache(function, Js::PropertyIds::prototype, scriptContext));
JavascriptGenerator* generator = scriptContext->GetLibrary()->CreateGenerator(heapArgs, asyncGeneratorFunction->scriptFunction, prototype);
generator->SetIsAsync();
generator->InitialiseAsyncGenerator(scriptContext);
// Set the prototype from constructor
JavascriptOperators::OrdinaryCreateFromConstructor(function, generator, prototype, scriptContext);
return generator;
}

Expand Down
8 changes: 6 additions & 2 deletions lib/Runtime/Library/JavascriptLibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,9 @@ namespace Js
NullTypeHandler<false>::GetDefaultInstance(), true, true);

generatorConstructorPrototypeObjectType->SetHasNoEnumerableProperties(true);

generatorType = DynamicType::New(scriptContext, TypeIds_Generator, generatorPrototype, nullptr,
PathTypeHandlerNoAttr::New(scriptContext, this->GetRootPath(), 0, 0, 0, true, true), true, true);
}

if (config->IsES2018AsyncIterationEnabled())
Expand Down Expand Up @@ -6120,8 +6123,9 @@ namespace Js
JavascriptGenerator* JavascriptLibrary::CreateGenerator(Arguments& args, ScriptFunction* scriptFunction, RecyclableObject* prototype)
{
Assert(scriptContext->GetConfig()->IsES6GeneratorsEnabled());
DynamicType* generatorType = CreateGeneratorType(prototype);
return JavascriptGenerator::New(this->GetRecycler(), generatorType, args, scriptFunction);
JavascriptGenerator* generator = JavascriptGenerator::New(this->GetRecycler(), this->generatorType, args, scriptFunction);
JavascriptObject::ChangePrototype(generator, prototype, true, scriptContext);
return generator;
}

JavascriptError* JavascriptLibrary::CreateError()
Expand Down
1 change: 1 addition & 0 deletions lib/Runtime/Library/JavascriptLibrary.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ namespace Js
Field(DynamicType *) stringIteratorType;
Field(DynamicType *) promiseType;
Field(DynamicType *) listIteratorType;
Field(DynamicType *) generatorType;

Field(JavascriptFunction*) builtinFunctions[BuiltinFunction::Count];

Expand Down