-
Notifications
You must be signed in to change notification settings - Fork 310
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
feature/labelInLoop #479
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.
@@ -2064,6 +2064,22 @@ final class LoopContinue: JsOperation { | |||
} | |||
} | |||
|
|||
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?
@@ -709,6 +709,12 @@ public class FuzzILLifter: Lifter { | |||
case .loopContinue: | |||
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)")
@@ -1415,6 +1415,14 @@ public let CodeGenerators: [CodeGenerator] = [ | |||
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
?
@@ -2889,4 +2889,287 @@ class LifterTests: XCTestCase { | |||
""" | |||
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 liketestNestedBreakToSameLabel
or 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
BreakNested
andContinueNested
are added. The two JS instructions both require a number input that represents the depth of the loop that the instruction wants tobreak
orcontinue
, 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
begin
andend
block. For example,beginWhileLoopBody
. Each time it is loaded,actualLoopDepth
is incremented, and the start position of this loop in the overall code string is recorded. WhenendWhileLoop
is loaded,actualLoopDepth
is decremented, and the recorded position for the current loop is popped.During the
breakNested
lifting 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. ThecontinueNested
process follows the same steps.