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

Raise params to the next frame when pushing #2145

Merged
merged 5 commits into from
Jul 17, 2019

Conversation

mzgoddard
Copy link
Contributor

@mzgoddard mzgoddard commented Apr 25, 2019

Resolves

Runtime performance related to stack operations including procedure parameters and util.stackFrame (thread.stackFrame.executionContext).

Proposed Changes

Related to #2147 and #2148.

  • Remove procedures_definition block function
  • Remove use of utils.stackFrame in procedures_call
  • _StackFrame.params is always set
  • Set params after starting a procedure
  • Carry params into deeper stack frames
  • Set params to null prototype objects
  • executionContext is always set
  • Reset executionContext when needed instead of setting it on demand
  • Move reported from stackFrame to thread
  • Clean up other reporting members
  • Add Thread.pointer
  • Add Thread.stackFrame

Reason for Changes

  • Remove procedures_definition block function

The VM supports blocks without functions. An empty block function takes time to work through finding the function, calling it, and figuring out what to do with the result. So remove it and get on to other work.

  • Remove use of utils.stackFrame in procedures_call

The VM handles the stack correctly and will execute procedures_call twice. (I'm guessing this was a workaround for a bug we incidentally solved at some point.)

  • _StackFrame.params is always set
  • Set params after starting a procedure
  • Carry params into deeper stack frames

Always set to some object we can look up and set parameter values faster. Carry params forward when pushing the stack lets us immediately access the current procedure's parameters. We don't need to look at params in any other stack frames.

  • Set params to null prototype objects

Since only Thread interacts with params we can use the benefits of a null prototype Object without the concerns of the same.

  • executionContext is always set
  • Reset executionContext when needed instead of setting it on demand

Resetting when we need to instead of always resetting can save us some time. Having executionContext already set lets us remove a branch statement when retrieving executionContext.

  • Move reported from stackFrame to thread
  • Clean up other reporting members

reported members are used for promise returned blocks. As a thread is paused while the promise resolves the thread only needs to store one set of reported members. Moving that to the thread we can remove that from StackFrame and not worry about needing to unset it.

  • Add Thread.pointer
  • Add Thread.stackFrame

Keep the "top" of the stack as direct members of the Thread. This way we can directly access them without needing to find out the length of the stack and get the last item.

@mzgoddard mzgoddard requested review from paulkaplan and kchadha April 25, 2019 20:10
@mzgoddard mzgoddard changed the title Raise params Raise params to the next frame when pushing Apr 25, 2019
@thisandagain thisandagain added this to the May 2019 milestone Apr 29, 2019
@@ -13,46 +13,39 @@ class Scratch3ProcedureBlocks {
*/
getPrimitives () {
return {
procedures_definition: this.definition,
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add a comment in place of this line for why procedures_definition isn't mentioned as a one of the 'primitives'

Copy link
Contributor

Choose a reason for hiding this comment

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

On second thought, what if we made procedures_definition: null an entry here instead of removing it? In general, I don't like having undefined be something meaningful...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah. Hmm. Setting this to null, we need to change a test in the runtime method that register's primitives. It tries to bind null like its a function. That is to say the register primitives algorithm only accepts functions currently.

I'd guess we'll need to change other things in there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

procedures_call: this.call,
argument_reporter_string_number: this.argumentReporterStringNumber,
argument_reporter_boolean: this.argumentReporterBoolean
};
}

definition () {
// No-op: execute the blocks.
Copy link
Contributor

Choose a reason for hiding this comment

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

@cwillisf Do you have any concerns for extensionification of custom procs that the procedure definition function will not exist?

In general we expect either opcode or both opcode and func to be defined for a given block right? We should make sure we start to handle these non-existent functions well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We already handle non-existent functions (hats like event_whenflagclicked and shadows). Do we instead need to handle confirming that non-existence is expected?

src/engine/thread.js Outdated Show resolved Hide resolved
src/engine/thread.js Outdated Show resolved Hide resolved
src/engine/thread.js Outdated Show resolved Hide resolved
src/engine/thread.js Outdated Show resolved Hide resolved
src/engine/thread.js Outdated Show resolved Hide resolved
src/engine/thread.js Outdated Show resolved Hide resolved
src/engine/thread.js Outdated Show resolved Hide resolved
src/engine/thread.js Outdated Show resolved Hide resolved
src/engine/thread.js Outdated Show resolved Hide resolved
stackFrame.params = {};
}
const stackFrame = this.stackFrame;
stackFrame.params = Object.create(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

does it make sense to just use defaultParams here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to guard against overwriting already existing params here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ooop. That is what I meant to do. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm. Aw no, I think I misunderstood when I last responded.

This is compatible with Scratch 2 procedure behaviour to my knowledge. This PR swaps the order the stack is changed. In this PR the stack is pushed and then parameters are set. The parent procedure's stack frame is left alone and the new stack frame will start a new set of parameters.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah okay.

That still leaves the question of whether we should use defaultParams here instead of explicitly using Object.create(null) again.

src/engine/thread.js Outdated Show resolved Hide resolved
Copy link
Contributor

@kchadha kchadha left a comment

Choose a reason for hiding this comment

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

Left some questions and comments inline. It also looks like tests are failing.

mzgoddard added 3 commits June 3, 2019 15:49
The engine supports blocks that do not have defined functions.
- Add pointer member to thread. This is the current executing block.
- Add stackFrame member to thread. This is the current frame
  information like procedure parameters.

This is a step potentially towards stack-less threads. With further
modifications we could have stack and stackFrames be null if a script
does not call a procedure.
@mzgoddard
Copy link
Contributor Author

I fixed the bug you found @kchadha. It was the dirty isLoop problem. I've added some tests to test/unit/engine_sequencer.js to protect against future regression.

@kchadha
Copy link
Contributor

kchadha commented Jun 24, 2019

@mzgoddard just noticed what seems like another bug. I was testing project 304890904 from production locally w/the changes from this PR.

Things seem to work great until I try to click on the original big red circle to delete it, and then it seems like the whole browser tab freezes.

- Test that stopThisScript in a procedure stays in the procedure and
  moves it to the end where Sequencer can pop it normally
@mzgoddard
Copy link
Contributor Author

@kchadha Here's a minimal repro.

Screen Shot 0031-06-25 at 12 07 16 PM

I found, tested, and fixed two bugs in Thread.stopThisScript from that. One I added in the PR and a pre-existing one. The executionContext use by procedures_call was occluding the bug.

The pre-existing bug is caused by a technical incompatibility with scratch 2. scratch-flash does one more thing in stopThisScript, it sets the block pointer to null scratch-flash/src/interpreter/Thread.as#L115. That would let Sequencer pop the procedure. Instead develop currently pops to the procedure call block, Sequencer leaves it alone and reruns the procedure call. Necessitating the executionContext check work around.

There is a relevant unit test on stopThisScript but it looks like it depends on the technical incompatibility by looking at the stack and looking for the procedure call block id. It should instead check that stack is null.

@kchadha
Copy link
Contributor

kchadha commented Jul 8, 2019

@mzgoddard I re-tested this PR after the bugfix you added, and I think it looks good to go. Could you add a code comment about the line you added in stopThisScript about why pushing null onto the stack is significant (and/or what bug it was fixing)? After that LGTM.

@mzgoddard
Copy link
Contributor Author

  • Add a comment inside stopThisScript on pushing null

Copy link
Contributor

@kchadha kchadha left a comment

Choose a reason for hiding this comment

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

LGTM

@BryceLTaylor
Copy link

BryceLTaylor commented Dec 3, 2019

Testing Plan:

Description: This pr changes how Custom procedures store variables so they are more easily accessible to the vm while running the block.

To test:
Make a custom procedure with a number input, a text input (same as number but we'll use it for text) and a boolean input
Put the block into a forever loop
Add code to the procedure definition while it's running
Add an if statement that uses the boolean input
Add an if / then block using the variables
add a statement that uses the number input
add a statement that uses the text input
Change the values of the inputs while the stack is running
Use a "say 'word' for [time]" block at the end of the stack
Use a "say 'word' for [time]" block in the beginning/middle of the stack

Make a custom procedure that runs without screen refresh and repeat the above steps
Edit the block and add another variable while running
edit the block and delete a variable while running

call the block recursively

Make sure to add the variables from the custom procedure everywhere

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

Successfully merging this pull request may close these issues.

6 participants