Skip to content

Commit c76ea0e

Browse files
author
Samuel Groß
committed
Redesign For-Loops in FuzzIL
Similar to while- and do-while loops, for-loops now consists of multiple blocks: BeginForLoopInitializer // ... // v0 = initial value of the (single) loop variable BeginForLoopCondition v0 -> v1 // v1 = current value of the (single) loop variable // ... BeginForLoopAfterthought -> v2 // v2 = current value of the (single) loop variable // ... BeginForLoopBody -> v3 // v3 = current value of the (single) loop variable // ... EndForLoop A simple for-loop will be lifted to: for (let i = init; cond; afterthought) { body(i); } However, more (and less) complex for loops are also possible. Some examples include: for (;;) { body(); } for (let i4 = f1(), i5 = f2(); f3(i4), f4(i5); f5(i5), f6(i4)) { body(i4, i5); } for (let [i6, i7] = (() => { const v1 = f(); const v3 = g(); h(); return [v1, v3]; })(); i6 < i7; i6 += 2) { body(i6, i7); } See the added tests for yet more examples.
1 parent e25fa16 commit c76ea0e

33 files changed

Lines changed: 1792 additions & 599 deletions

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,9 +2190,42 @@ public class ProgramBuilder {
21902190
emit(EndDoWhileLoop(), withInputs: [cond])
21912191
}
21922192

2193-
public func buildForLoop(_ start: Variable, _ comparator: Comparator, _ end: Variable, _ op: BinaryOperator, _ rhs: Variable, _ body: (Variable) -> ()) {
2194-
let i = emit(BeginForLoop(comparator: comparator, op: op), withInputs: [start, end, rhs]).innerOutput
2195-
body(i)
2193+
// Build a simple for loop that declares one loop variable.
2194+
public func buildForLoop(i initializer: () -> Variable, _ cond: (Variable) -> Variable, _ afterthought: (Variable) -> (), _ body: (Variable) -> ()) {
2195+
emit(BeginForLoopInitializer())
2196+
let initialValue = initializer()
2197+
var loopVar = emit(BeginForLoopCondition(numLoopVariables: 1), withInputs: [initialValue]).innerOutput
2198+
let cond = cond(loopVar)
2199+
loopVar = emit(BeginForLoopAfterthought(numLoopVariables: 1), withInputs: [cond]).innerOutput
2200+
afterthought(loopVar)
2201+
loopVar = emit(BeginForLoopBody(numLoopVariables: 1)).innerOutput
2202+
body(loopVar)
2203+
emit(EndForLoop())
2204+
}
2205+
2206+
// Build arbitrarily complex for loops without any loop variables.
2207+
public func buildForLoop(_ initializer: (() -> ())? = nil, _ cond: (() -> Variable)? = nil, _ afterthought: (() -> ())? = nil, _ body: () -> ()) {
2208+
emit(BeginForLoopInitializer())
2209+
initializer?()
2210+
emit(BeginForLoopCondition(numLoopVariables: 0))
2211+
let cond = cond?() ?? loadBool(true)
2212+
emit(BeginForLoopAfterthought(numLoopVariables: 0), withInputs: [cond])
2213+
afterthought?()
2214+
emit(BeginForLoopBody(numLoopVariables: 0))
2215+
body()
2216+
emit(EndForLoop())
2217+
}
2218+
2219+
// Build arbitrarily complex for loops with one or more loop variables.
2220+
public func buildForLoop(_ initializer: () -> [Variable], _ cond: (([Variable]) -> Variable)? = nil, _ afterthought: (([Variable]) -> ())? = nil, _ body: ([Variable]) -> ()) {
2221+
emit(BeginForLoopInitializer())
2222+
let initialValues = initializer()
2223+
var loopVars = emit(BeginForLoopCondition(numLoopVariables: initialValues.count), withInputs: initialValues).innerOutputs
2224+
let cond = cond?(Array(loopVars)) ?? loadBool(true)
2225+
loopVars = emit(BeginForLoopAfterthought(numLoopVariables: initialValues.count), withInputs: [cond]).innerOutputs
2226+
afterthought?(Array(loopVars))
2227+
loopVars = emit(BeginForLoopBody(numLoopVariables: initialValues.count)).innerOutputs
2228+
body(Array(loopVars))
21962229
emit(EndForLoop())
21972230
}
21982231

@@ -2209,12 +2242,12 @@ public class ProgramBuilder {
22092242
}
22102243

22112244
public func buildForOfLoop(_ obj: Variable, selecting indices: [Int64], hasRestElement: Bool = false, _ body: ([Variable]) -> ()) {
2212-
let instr = emit(BeginForOfWithDestructLoop(indices: indices, hasRestElement: hasRestElement), withInputs: [obj])
2245+
let instr = emit(BeginForOfLoopWithDestruct(indices: indices, hasRestElement: hasRestElement), withInputs: [obj])
22132246
body(Array(instr.innerOutputs))
22142247
emit(EndForOfLoop())
22152248
}
22162249

2217-
public func buildRepeat(n numIterations: Int, _ body: (Variable) -> ()) {
2250+
public func buildRepeatLoop(n numIterations: Int, _ body: (Variable) -> ()) {
22182251
let i = emit(BeginRepeatLoop(iterations: numIterations)).innerOutput
22192252
body(i)
22202253
emit(EndRepeatLoop())

Sources/Fuzzilli/CodeGen/CodeGenerators.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,15 +1176,28 @@ public let CodeGenerators: [CodeGenerator] = [
11761176
}, while: { b.compare(loopVar, with: b.loadInt(Int64.random(in: 0...10)), using: .lessThan) })
11771177
},
11781178

1179-
RecursiveCodeGenerator("ForLoopGenerator") { b in
1180-
let start = b.reuseOrLoadInt(0)
1181-
let end = b.reuseOrLoadInt(Int64.random(in: 0...10))
1182-
let step = b.reuseOrLoadInt(1)
1183-
b.buildForLoop(start, .lessThan, end, .Add, step) { _ in
1179+
RecursiveCodeGenerator("SimpleForLoopGenerator") { b in
1180+
b.buildForLoop(i: { b.loadInt(0) }, { i in b.compare(i, with: b.loadInt(Int64.random(in: 0...10)), using: .lessThan) }, { i in b.unary(.PostInc, i) }) { _ in
11841181
b.buildRecursive()
11851182
}
11861183
},
11871184

1185+
RecursiveCodeGenerator("ComplexForLoopGenerator") { b in
1186+
if probability(0.5) {
1187+
// Generate a for-loop without any loop variables.
1188+
let counter = b.loadInt(10)
1189+
b.buildForLoop({}, { b.unary(.PostDec, counter) }) {
1190+
b.buildRecursive()
1191+
}
1192+
} else {
1193+
// Generate a for-loop with two loop variables.
1194+
// TODO could also generate loops with even more loop variables?
1195+
b.buildForLoop({ return [b.loadInt(0), b.loadInt(10)] }, { vs in b.compare(vs[0], with: vs[1], using: .lessThan) }, { vs in b.unary(.PostInc, vs[0]); b.unary(.PostDec, vs[0]) }) { _ in
1196+
b.buildRecursive()
1197+
}
1198+
}
1199+
},
1200+
11881201
RecursiveCodeGenerator("ForInLoopGenerator", input: .object()) { b, obj in
11891202
b.buildForInLoop(obj) { _ in
11901203
b.buildRecursive()
@@ -1218,7 +1231,7 @@ public let CodeGenerators: [CodeGenerator] = [
12181231

12191232
RecursiveCodeGenerator("RepeatLoopGenerator") { b in
12201233
let numIterations = Int.random(in: 2...100)
1221-
b.buildRepeat(n: numIterations) { _ in
1234+
b.buildRepeatLoop(n: numIterations) { _ in
12221235
b.buildRecursive()
12231236
}
12241237
},
@@ -1463,7 +1476,7 @@ public let CodeGenerators: [CodeGenerator] = [
14631476
b.buildRecursive(block: 2, of: 3)
14641477
b.doReturn(b.randomVariable())
14651478
}
1466-
b.buildRepeat(n: numIterations) { i in
1479+
b.buildRepeatLoop(n: numIterations) { i in
14671480
b.buildIf(b.compare(i, with: lastIteration, using: .equal)) {
14681481
b.buildRecursive(block: 3, of: 3, n: 3)
14691482
}

Sources/Fuzzilli/CodeGen/ProgramTemplates.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public let ProgramTemplates = [
4444
b.build(n: genSize)
4545

4646
// trigger JIT
47-
b.buildForLoop(b.loadInt(0), .lessThan, b.loadInt(100), .Add, b.loadInt(1)) { args in
47+
b.buildRepeatLoop(n: 100) { _ in
4848
b.callFunction(f, withArgs: b.generateCallArguments(for: signature))
4949
}
5050

@@ -53,7 +53,7 @@ public let ProgramTemplates = [
5353
b.callFunction(f, withArgs: b.generateCallArguments(for: signature))
5454

5555
// maybe trigger recompilation
56-
b.buildForLoop(b.loadInt(0), .lessThan, b.loadInt(100), .Add, b.loadInt(1)) { args in
56+
b.buildRepeatLoop(n: 100) { _ in
5757
b.callFunction(f, withArgs: b.generateCallArguments(for: signature))
5858
}
5959

@@ -99,12 +99,12 @@ public let ProgramTemplates = [
9999
b.build(n: genSize)
100100

101101
// trigger JIT for first function
102-
b.buildForLoop(b.loadInt(0), .lessThan, b.loadInt(100), .Add, b.loadInt(1)) { args in
102+
b.buildRepeatLoop(n: 100) { _ in
103103
b.callFunction(f1, withArgs: b.generateCallArguments(for: signature1))
104104
}
105105

106106
// trigger JIT for second function
107-
b.buildForLoop(b.loadInt(0), .lessThan, b.loadInt(100), .Add, b.loadInt(1)) { args in
107+
b.buildRepeatLoop(n: 100) { _ in
108108
b.callFunction(f2, withArgs: b.generateCallArguments(for: signature2))
109109
}
110110

@@ -115,12 +115,12 @@ public let ProgramTemplates = [
115115
b.callFunction(f1, withArgs: b.generateCallArguments(for: signature1))
116116

117117
// maybe trigger recompilation
118-
b.buildForLoop(b.loadInt(0), .lessThan, b.loadInt(100), .Add, b.loadInt(1)) { args in
118+
b.buildRepeatLoop(n: 100) { _ in
119119
b.callFunction(f1, withArgs: b.generateCallArguments(for: signature1))
120120
}
121121

122122
// maybe trigger recompilation
123-
b.buildForLoop(b.loadInt(0), .lessThan, b.loadInt(100), .Add, b.loadInt(1)) { args in
123+
b.buildRepeatLoop(n: 100) { _ in
124124
b.callFunction(f2, withArgs: b.generateCallArguments(for: signature2))
125125
}
126126

@@ -156,7 +156,7 @@ public let ProgramTemplates = [
156156

157157
b.callFunction(f, withArgs: initialArgs)
158158

159-
b.buildForLoop(b.loadInt(0), .lessThan, b.loadInt(100), .Add, b.loadInt(1)) { _ in
159+
b.buildRepeatLoop(n: 100) { _ in
160160
b.callFunction(f, withArgs: optimizationArgs)
161161
}
162162

Sources/Fuzzilli/Compiler/Compiler.swift

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -200,55 +200,48 @@ public class JavaScriptCompiler {
200200
emit(EndDoWhileLoop(), withInputs: [cond])
201201

202202
case .forLoop(let forLoop):
203-
// TODO change the IL to avoid this special handling.
204-
205-
// Process initializer.
206-
let initializer = forLoop.init_p;
207-
guard initializer.hasValue else {
208-
throw CompilerError.invalidNodeError("Expected an initial value in the for loop initializer")
209-
}
210-
let start = try compileExpression(initializer.value)
203+
try enterNewScope {
204+
var loopVariables = [String]()
205+
206+
// Process initializer.
207+
var initialLoopVariableValues = [Variable]()
208+
emit(BeginForLoopInitializer())
209+
if let initializer = forLoop.initializer {
210+
switch initializer {
211+
case .declaration(let declaration):
212+
for declarator in declaration.declarations {
213+
loopVariables.append(declarator.name)
214+
initialLoopVariableValues.append(try compileExpression(declarator.value))
215+
}
216+
case .expression(let expression):
217+
try compileExpression(expression)
218+
}
219+
}
211220

212-
// Process test expression.
213-
guard case .binaryExpression(let test) = forLoop.test.expression, let comparator = Comparator(rawValue: test.operator) else {
214-
throw CompilerError.invalidNodeError("Expected a comparison as part of the test of a for loop")
215-
}
216-
guard case .identifier(let identifier) = test.lhs.expression else {
217-
throw CompilerError.invalidNodeError("Expected an identifier as lhs of the test expression in a for loop")
218-
}
219-
guard identifier.name == initializer.name else {
220-
throw CompilerError.invalidNodeError("Expected the lhs of the test expression in a for loop to be the loop variable")
221-
}
222-
let end = try compileExpression(test.rhs)
221+
// Process condition.
222+
var outputs = emit(BeginForLoopCondition(numLoopVariables: loopVariables.count), withInputs: initialLoopVariableValues).innerOutputs
223+
zip(loopVariables, outputs).forEach({ map($0, to: $1 )})
224+
let cond: Variable
225+
if forLoop.hasCondition {
226+
cond = try compileExpression(forLoop.condition)
227+
} else {
228+
cond = emit(LoadBoolean(value: true)).output
229+
}
223230

224-
// Process update expression.
225-
guard case .updateExpression(let update) = forLoop.update.expression else {
226-
throw CompilerError.invalidNodeError("Expected an update expression as final part of a for loop")
227-
}
228-
guard case .identifier(let identifier) = update.argument.expression else {
229-
throw CompilerError.invalidNodeError("Expected an identifier as argument to the update expression in a for loop")
230-
}
231-
guard identifier.name == initializer.name else {
232-
throw CompilerError.invalidNodeError("Expected the update expression in a for loop to update the loop variable")
233-
}
234-
let one = emit(LoadInteger(value: 1)).output
235-
let op: BinaryOperator
236-
switch update.operator {
237-
case "++":
238-
op = .Add
239-
case "--":
240-
op = .Sub
241-
default:
242-
throw CompilerError.invalidNodeError("Unexpected operator in for loop update: \(update.operator)")
243-
}
231+
// Process afterthought.
232+
outputs = emit(BeginForLoopAfterthought(numLoopVariables: loopVariables.count), withInputs: [cond]).innerOutputs
233+
zip(loopVariables, outputs).forEach({ remap($0, to: $1 )})
234+
if forLoop.hasAfterthought {
235+
try compileExpression(forLoop.afterthought)
236+
}
244237

245-
let loopVar = emit(BeginForLoop(comparator: comparator, op: op), withInputs: [start, end, one]).innerOutput
246-
try enterNewScope {
247-
map(initializer.name, to: loopVar)
238+
// Process body
239+
outputs = emit(BeginForLoopBody(numLoopVariables: loopVariables.count)).innerOutputs
240+
zip(loopVariables, outputs).forEach({ remap($0, to: $1 )})
248241
try compileBody(forLoop.body)
249-
}
250242

251-
emit(EndForLoop())
243+
emit(EndForLoop())
244+
}
252245

253246
case .forInLoop(let forInLoop):
254247
let initializer = forInLoop.left;
@@ -282,6 +275,13 @@ public class JavaScriptCompiler {
282275

283276
emit(EndForOfLoop())
284277

278+
case .breakStatement:
279+
// TODO currently we assume this is a LoopBreak, but once we support switch-statements, it could also be a SwitchBreak
280+
emit(LoopBreak())
281+
282+
case .continueStatement:
283+
emit(LoopContinue())
284+
285285
case .tryStatement(let tryStatement):
286286
emit(BeginTry())
287287
try enterNewScope {
@@ -791,6 +791,11 @@ public class JavaScriptCompiler {
791791
scopes.top[identifier] = v
792792
}
793793

794+
private func remap(_ identifier: String, to v: Variable) {
795+
assert(scopes.top[identifier] != nil)
796+
scopes.top[identifier] = v
797+
}
798+
794799
private func mapParameters(_ parameters: [Compiler_Protobuf_Parameter], to variables: ArraySlice<Variable>) {
795800
assert(parameters.count == variables.count)
796801
for (param, v) in zip(parameters, variables) {

0 commit comments

Comments
 (0)