-
Notifications
You must be signed in to change notification settings - Fork 349
feature/labelInLoop #479
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
base: main
Are you sure you want to change the base?
feature/labelInLoop #479
Conversation
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.
Thanks for working on this! First high-level round of feedback
| override var opcode: Opcode { .breakNested(self) } | ||
|
|
||
| init() { | ||
| super.init(numInputs: 1, attributes: [.isJump], requiredContext: [.javascript, .loop]) |
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.
Having the depth as input will be tricky, again because the mutator will change inputs around arbitrarily and you'll quickly get strings or objects or other stuff as depth. Instead, I'd recommend making the depth a property of this operation:
final class BreakNested: JsOperation {
override var opcode: Opcode { .breakNested(self) }
let depth: Int
init(depth: Int) {
...
}
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 can then also let the OperationMutator mutate the depth property of these.
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.
Your analysis has been very helpful to me. Thank you so much! I will complete the revisions as soon as possible.
| } | ||
| } | ||
|
|
||
| final class BreakNested: JsOperation { |
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.
Is my understanding correct that these can only be used inside loops? Or are they also usable outside of loops in certain circumstances?
If they must always be in a loop: maybe we should also call these LoopBreakNested or something like that to stay consistent with LoopBreak
If they can also appear elsewhere: should we drop the .loop context requirement?
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.
Yes, these can only be used in loops. This is an inappropriate naming issue, and I will later rename it to LoopBreakNested and LoopContinueNested.
facb0f9 to
0702ab2
Compare
0702ab2 to
0f003d9
Compare
|
Hi @saelo, I have made improvements in the following three aspects based on your insightful feedback:
|
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.
Thanks! So I played around a bit more, and it looks like while continue has to appear inside a loop, break can appear in any other statement as well. For example, the following are all valid:
label1:
if (true) {
break label1;
console.log('not reached');
} else {
break label1;
console.log('not reached');
}
label2: break label2;
label3: {
try {
break label3;
console.log('not reached');
} catch (e) {}
}
label4: with({}) {
break label4;
console.log('not reached');
}
label5: switch(1) {
case 1: break label5;
case 2: console.log('not reached');
}
What is not valid is something like label1: foo, break label1, bar (I'm not even sure why) or label1: foo; break label1; bar (that makes sense, now we have multiple statements). What's also not valid are things involving functions and similar:
// These are all invalid
label6: function foo() { break label6; }
label7: () => { break label7; }
label8: class Foo { constructor() { break label8; } }
So that'll make the lifting more complicated I guess, and also mean that we'll have to support the nested break in every JS context. Since break and continue behave differently, I would probably recommend splitting this PR into two separate PRs: one for nested breaks and one for nested continues. Then I think we should first get nested breaks to work (these seem more interesting), then nested continues. Does that sound good?
| w.emit("Continue") | ||
|
|
||
| case .loopBreakNested: | ||
| w.emit("LoopBreakNested") |
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.
Nit: maybe add the depth here and below:
case .loopBreakNested(let op):
w.emit("LoopBreakNested \(op.depth)")
| b.loopContinue() | ||
| }, | ||
|
|
||
| CodeGenerator("LoopLabelBreakGenerator", inContext: .loop) { b in |
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.
Nit: maybe these should also be called LoopNestedBreakGenerator and LoopNestedContinueGenerator?
| XCTAssertEqual(actual, expected) | ||
| } | ||
|
|
||
| func testForLoop(){ |
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.
There are already a number of tests for the various loop types, e.g. testForLoopLifting1. I think these tests should focus on the nested breaks. The loop type is probably less important. More important is testing the different ways we can break and continue. For example:
- breaking out of a loop at depth 1
- breaking out of a loop at depth 2
- same for continue
- breaking/continuing to the same label multiple times from different depths
- breaking and continuing to the same label
- etc.
I think your tests already basically cover all these scenarios, so I would suggest either renaming them to something specific liketestNestedBreakToSameLabelor justtestNestedBreakLifting1,testNestedBreakLifting2, etc. with a short comment at the start that describes the scenario being tested. Then you can still just use different loop types for each test, but that's not the important part.
According to feedback from #467, I reimplemented the feature of the label statement under the context of loop structure. The design ideas are as follows:
The operations
BreakNestedandContinueNestedare added. The two JS instructions both require a number input that represents the depth of the loop that the instruction wants tobreakorcontinue, and if the number exceeds the actual loop depth, then the actual loop depth is equal todepth %= actualLoopDepth.In JSLifter, any loop structure has a
beginandendblock. For example,beginWhileLoopBody. Each time it is loaded,actualLoopDepthis incremented, and the start position of this loop in the overall code string is recorded. WhenendWhileLoopis loaded,actualLoopDepthis decremented, and the recorded position for the current loop is popped.During the
breakNestedlifting process, the expected depth of the target loop to break is first determined usinginput % actualLoopDepth. Then, the starting position of the target loop is obtained from the recorded loop stack. Simultaneously, it is checked whether a label already exists at that position. If not, the label will be inserted, all subsequent loop starting positions are updated, and the position is marked as inserted. ThecontinueNestedprocess follows the same steps.