Skip to content

Implement TCO into ReturnCallIndirect and ReturnCallRef in JIT - #450

Open
makachanm wants to merge 4 commits into
Samsung:mainfrom
makachanm:impl_tco
Open

Implement TCO into ReturnCallIndirect and ReturnCallRef in JIT#450
makachanm wants to merge 4 commits into
Samsung:mainfrom
makachanm:impl_tco

Conversation

@makachanm

Copy link
Copy Markdown
Contributor

I found ReturnCallIndirect and ReturnCallRef works in fallback mode that makes stack memory consumption is not a consist.
Problem is they not use a fully stack-frame destructive way to jumping in another function to function situation(so call non-self-call) and also it may be problematic in runtime determination of jump address that used in ReturnCallIndirect and ReturnCallRef.

It cannot handled in in-function flow control method, so I rewrite all JIT TCO code into fully trampoline loop.
It can now full ALL pass the test without stack overflow.

@zherczeg

Copy link
Copy Markdown
Collaborator

The current model is quite complex (and specialized) for me, and add more complexity is proposed. I would prefer a cleaner solution.

Perhpas this while should also contain the jit case: https://github.com/Samsung/walrus/blob/main/src/interpreter/Interpreter.h#L123

Then the restart mechanism could be here in some way. Maybe through a special exception or a return code. This way anything could restart anything, there would be less code specialization. The question is not simple, but it is worth thinking about it to avoid too many special cases.

Comment thread src/interpreter/Interpreter.cpp Outdated
Comment on lines +1731 to +1733
if (UNLIKELY(result == TailCallResult::Restart)) {
return nullptr;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

TailCallResult::Restart is used only when JIT is enabled (WALRUS_ENABLE_JIT)
So, please wrap this if statement with TailCallResult::Restart macro

Comment thread src/interpreter/Interpreter.cpp Outdated
Comment on lines +1763 to +1765
if (UNLIKELY(result == TailCallResult::Restart)) {
return nullptr;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ditto

Comment thread src/interpreter/Interpreter.cpp Outdated
Comment on lines +1790 to +1792
if (UNLIKELY(result == TailCallResult::Restart)) {
return nullptr;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ditto

Comment thread src/jit/CallInl.h Outdated

Operand* operand = instr->operands();

bool isReturnCall = instr->opcode() == ByteCode::ReturnCallOpcode

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This isReturnCall name is quite confusing
What about renaming it as isTailCall?

Comment thread src/jit/CallInl.h Outdated
Comment on lines +253 to +258
if (instr->opcode() == ByteCode::ReturnCallOpcode) {
ReturnCall* returnCall = reinterpret_cast<ReturnCall*>(instr->byteCode());
isSelfDirect = context->compiler->module()->function(returnCall->index()) == context->compiler->moduleFunction();
}

if (isSelfDirect) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems that these 2 if conditions could be merged,
and I think that merging is much clearer

@zherczeg

Copy link
Copy Markdown
Collaborator

I am currently working on table64 support for callIndirect / returnCallIndirect #453

@makachanm

Copy link
Copy Markdown
Contributor Author

This way is more incomplete to cover all TCO situation can happen, but not using a trampoline method.

For example

  • Compiled Function -> Non-Compiled Function will be fallback.
  • Compiled Function -> Outer Module Function will be fallback.

but I don't think these case will become a practical issue.

Comment thread src/jit/CallInl.h Outdated
{
Vector<size_t> paramBuffer;
paramBuffer.resizeWithUninitializedValues(parameterOffsetCount);
size_t stackBuffer[64];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Any specific reason for this 64 size of buffer?
IMO it's better to allocate buffer memory using ALLOCA
ALLOCA allocates the necessary memory on the C stack, which is reclaimed automatically.
So, there is no need to allocate a fixed-size buffer (size: 64) or use the heap (malloc)

Comment thread src/jit/CallInl.h
Comment on lines +225 to +228
if (idx >= table->size()) {
context->error = ExecutionContext::UndefinedElementError;
return ExecutionContext::UndefinedElementError;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I wonder if these errors occurred in the callbacks are handled correctly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I checked that route handles error correctly and throws out into surface. maybe the error code is seems to mismatched, but it is actually correct error code that used in these situation. code wants call the non-existing element in table, so it can considered as correct error code.

Comment thread src/jit/CallInl.h Outdated
Comment on lines +184 to +186
if (LIKELY(targetJitFunction != nullptr && targetJitFunction->isCompiled()
&& targetJitFunction->instanceConstData() == context->currentInstanceConstData
&& targetModuleFunction->requiredStackSize() <= context->frameCapacity)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AFAIK TCO JIT is only applicable when the callee's stack size is smaller than that of caller.
For the other case, what about allocating a new stack frame and switching to it like that of interpreter's TCO?

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.

3 participants