Skip to content

Commit a84cf7c

Browse files
LiedtkeV8-internal LUCI CQ
authored andcommitted
[wasm] Add support for loops with parameters and result value
Change-Id: Ia53e92e8f50e5e26ee93d4ef27e57f5d995b6d26 Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/7967833 Reviewed-by: Carl Smith <[email protected]> Commit-Queue: Matthias Liedtke <[email protected]>
1 parent 92f9eb9 commit a84cf7c

File tree

10 files changed

+104
-20
lines changed

10 files changed

+104
-20
lines changed

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3294,6 +3294,13 @@ public class ProgramBuilder {
32943294
b.emit(WasmEndLoop())
32953295
}
32963296

3297+
@discardableResult
3298+
public func wasmBuildLoop(with signature: Signature, args: [Variable], body: (Variable, [Variable]) -> Variable) -> Variable {
3299+
let instr = b.emit(WasmBeginLoop(with: signature), withInputs: args)
3300+
let fallthroughResult = body(instr.innerOutput(0), Array(instr.innerOutputs(1...)))
3301+
return b.emit(WasmEndLoop(outputType: signature.outputType), withInputs: [fallthroughResult]).output
3302+
}
3303+
32973304
public func wasmBuildLegacyTry(with signature: Signature, args: [Variable], body: (Variable, [Variable]) -> Void, catchAllBody: (() -> Void)? = nil) {
32983305
assert(signature.parameters.count == args.count)
32993306
let instr = b.emit(WasmBeginTry(with: signature), withInputs: args)
@@ -3567,9 +3574,10 @@ public class ProgramBuilder {
35673574
return params => returnType
35683575
}
35693576

3570-
public func randomWasmBlockOutputType() -> ILType {
3577+
public func randomWasmBlockOutputType(allowVoid: Bool = true) -> ILType {
35713578
// TODO(mliedtke): The selection of types is in sync with ProgramBuilder::randomWasmSignature(). This should allow more types.
3572-
return chooseUniform(from: [.wasmi32, .wasmi64, .wasmf32, .wasmf64, .nothing])
3579+
let possibleTypes: [ILType] = [.wasmi32, .wasmi64, .wasmf32, .wasmf64]
3580+
return chooseUniform(from: allowVoid ? possibleTypes + [.nothing] : possibleTypes)
35733581
}
35743582

35753583
@discardableResult

Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ public let codeGeneratorWeights = [
273273
"WasmBlockGenerator": 8,
274274
"WasmBlockWithSignatureGenerator": 8,
275275
"WasmLoopGenerator": 8,
276+
"WasmLoopWithSignatureGenerator": 8,
276277
"WasmLegacyTryGenerator": 8,
277278
"WasmLegacyCatchGenerator": 8,
278279
"WasmLegacyTryDelegateGenerator": 8,

Sources/Fuzzilli/CodeGen/WasmCodeGenerators.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,28 @@ public let WasmCodeGenerators: [CodeGenerator] = [
590590
}
591591
},
592592

593+
RecursiveCodeGenerator("WasmLoopWithSignatureGenerator", inContext: .wasmFunction) { b in
594+
let function = b.currentWasmModule.currentWasmFunction
595+
// Count upwards here to make it slightly more different from the other loop generator.
596+
// Also, instead of using reassign, this generator uses the signature to pass and update the loop counter.
597+
let randomArgs = (0..<5).map {_ in b.findVariable {b.type(of: $0).Is(.wasmPrimitive)}}.filter {$0 != nil}.map {$0!}
598+
let randomArgTypes = randomArgs.map{b.type(of: $0)}
599+
let args = [function.consti32(0)] + randomArgs
600+
let parameters = args.map {arg in Parameter.plain(b.type(of: arg))}
601+
let outputType = b.randomWasmBlockOutputType(allowVoid: false)
602+
// Note that due to the do-while style implementation, the actual iteration count is at least 1.
603+
let iterationCount = Int32.random(in: 0...16)
604+
605+
function.wasmBuildLoop(with: parameters => outputType, args: args) { label, loopArgs in
606+
b.buildRecursive()
607+
let loopCtr = function.wasmi32BinOp(args[0], function.consti32(1), binOpKind: .Add)
608+
let condition = function.wasmi32CompareOp(loopCtr, function.consti32(iterationCount), using: .Lt_s)
609+
let backedgeArgs = [loopCtr] + randomArgTypes.map{b.randomVariable(ofType: $0)!}
610+
function.wasmBranchIf(condition, to: label, args: backedgeArgs)
611+
return b.randomVariable(ofType: outputType) ?? function.generateRandomWasmVar(ofType: outputType)
612+
}
613+
},
614+
593615
RecursiveCodeGenerator("WasmLegacyTryGenerator", inContext: .wasmFunction) { b in
594616
let function = b.currentWasmModule.currentWasmFunction
595617
// Choose a few random wasm values as arguments if available.

Sources/Fuzzilli/FuzzIL/Instruction.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,8 +1203,10 @@ extension Instruction: ProtobufConvertible {
12031203
$0.parameters = convertParametersToWasmTypeEnums(op.signature.parameters)
12041204
$0.returnType = ILTypeToWasmTypeEnum(op.signature.outputType)
12051205
}
1206-
case .wasmEndLoop(_):
1207-
$0.wasmEndLoop = Fuzzilli_Protobuf_WasmEndLoop()
1206+
case .wasmEndLoop(let op):
1207+
$0.wasmEndLoop = Fuzzilli_Protobuf_WasmEndLoop.with {
1208+
$0.returnType = ILTypeToWasmTypeEnum(op.outputType)
1209+
}
12081210
case .wasmBeginTry(let op):
12091211
$0.wasmBeginTry = Fuzzilli_Protobuf_WasmBeginTry.with {
12101212
$0.parameters = convertParametersToWasmTypeEnums(op.signature.parameters)
@@ -2006,8 +2008,8 @@ extension Instruction: ProtobufConvertible {
20062008
Parameter.plain(WasmTypeEnumToILType(param))
20072009
})
20082010
op = WasmBeginLoop(with: parameters => WasmTypeEnumToILType(p.returnType))
2009-
case .wasmEndLoop(_):
2010-
op = WasmEndLoop()
2011+
case .wasmEndLoop(let p):
2012+
op = WasmEndLoop(outputType: WasmTypeEnumToILType(p.returnType))
20112013
case .wasmBeginTry(let p):
20122014
let parameters: [Parameter] = p.parameters.map({ param in
20132015
Parameter.plain(WasmTypeEnumToILType(param))

Sources/Fuzzilli/FuzzIL/WasmOperations.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,15 +1018,16 @@ final class WasmBeginLoop: WasmOperation {
10181018
// Note that different to all other blocks the loop's label parameters are the input types
10191019
// of the block, not the result types (because a branch to a loop label jumps to the
10201020
// beginning of the loop block instead of the end.)
1021-
super.init(outputType: .nothing, innerOutputTypes: [.label(parameterTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction])
1021+
super.init(inputTypes: parameterTypes, outputType: .nothing, innerOutputTypes: [.label(parameterTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction])
10221022
}
10231023
}
10241024

10251025
final class WasmEndLoop: WasmOperation {
10261026
override var opcode: Opcode { .wasmEndLoop(self) }
10271027

1028-
init() {
1029-
super.init(attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction])
1028+
init(outputType: ILType = .nothing) {
1029+
let inputTypes = outputType != .nothing ? [outputType] : []
1030+
super.init(inputTypes: inputTypes, outputType: outputType, attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction])
10301031
}
10311032
}
10321033

Sources/Fuzzilli/Lifting/FuzzILLifter.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -982,12 +982,18 @@ public class FuzzILLifter: Lifter {
982982
}
983983

984984
case .wasmBeginLoop(let op):
985-
w.emit("WasmBeginLoop L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
985+
let inputs = instr.inputs.map(lift).joined(separator: ", ")
986+
w.emit("WasmBeginLoop (\(op.signature)) [\(inputs)] -> L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))]")
986987
w.increaseIndentionLevel()
987988

988-
case .wasmEndLoop(_):
989+
case .wasmEndLoop(let op):
989990
w.decreaseIndentionLevel()
990-
w.emit("WasmEndLoop")
991+
let inputs = instr.inputs.map(lift).joined(separator: ", ")
992+
if op.numOutputs > 0 {
993+
w.emit("\(output()) <- WasmEndLoop \(inputs)")
994+
} else {
995+
w.emit("WasmEndLoop \(inputs)")
996+
}
991997

992998
case .wasmBeginTry(let op):
993999
w.emit("WasmBeginTry L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")

Sources/Fuzzilli/Lifting/WasmLifter.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,8 @@ public class WasmLifter {
891891
self.currentFunction!.labelBranchDepthMapping[instr.innerOutput(0)] = self.currentFunction!.variableAnalyzer.wasmBranchDepth
892892
// Needs typer analysis
893893
return true
894-
case .wasmBeginLoop(_):
894+
case .wasmBeginLoop(let op):
895+
registerSignature(op.signature)
895896
self.currentFunction!.labelBranchDepthMapping[instr.innerOutput(0)] = self.currentFunction!.variableAnalyzer.wasmBranchDepth
896897
// Needs typer analysis
897898
return true
@@ -996,7 +997,8 @@ public class WasmLifter {
996997

997998
// TODO(mliedtke): Reuse this for handling parameters in loops, if-else, ...
998999
if instr.op is WasmBeginCatch || instr.op is WasmBeginBlock || instr.op is WasmBeginTry
999-
|| instr.op is WasmBeginTryDelegate || instr.op is WasmBeginIf || instr.op is WasmBeginElse {
1000+
|| instr.op is WasmBeginTryDelegate || instr.op is WasmBeginIf || instr.op is WasmBeginElse
1001+
|| instr.op is WasmBeginLoop {
10001002
// As the parameters are pushed "in order" to the stack, they need to be popped in reverse order.
10011003
for innerOutput in instr.innerOutputs(1...).reversed() {
10021004
currentFunction!.spillLocal(forVariable: innerOutput)
@@ -1363,9 +1365,8 @@ public class WasmLifter {
13631365
// A Block can "produce" (push) an item on the value stack, just like a function. Similarly, a block can also have parameters.
13641366
// Ref: https://webassembly.github.io/spec/core/binary/instructions.html#binary-blocktype
13651367
return Data([0x02] + Leb128.unsignedEncode(signatureIndexMap[op.signature]!))
1366-
case .wasmBeginLoop(_):
1367-
// 0x03 is the loop instruction and 0x40 is the empty block type, just like in .wasmBeginBlock
1368-
return Data([0x03] + [0x40])
1368+
case .wasmBeginLoop(let op):
1369+
return Data([0x03] + Leb128.unsignedEncode(signatureIndexMap[op.signature]!))
13691370
case .wasmBeginTry(let op):
13701371
return Data([0x06] + Leb128.unsignedEncode(signatureIndexMap[op.signature]!))
13711372
case .wasmBeginTryDelegate(let op):

Sources/Fuzzilli/Protobuf/operations.pb.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3982,6 +3982,8 @@ public struct Fuzzilli_Protobuf_WasmEndLoop: Sendable {
39823982
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
39833983
// methods supported on all messages.
39843984

3985+
public var returnType: Fuzzilli_Protobuf_WasmILType = .consti32
3986+
39853987
public var unknownFields = SwiftProtobuf.UnknownStorage()
39863988

39873989
public init() {}
@@ -11759,18 +11761,31 @@ extension Fuzzilli_Protobuf_WasmBeginLoop: SwiftProtobuf.Message, SwiftProtobuf.
1175911761

1176011762
extension Fuzzilli_Protobuf_WasmEndLoop: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
1176111763
public static let protoMessageName: String = _protobuf_package + ".WasmEndLoop"
11762-
public static let _protobuf_nameMap = SwiftProtobuf._NameMap()
11764+
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
11765+
1: .same(proto: "returnType"),
11766+
]
1176311767

1176411768
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
11765-
// Load everything into unknown fields
11766-
while try decoder.nextFieldNumber() != nil {}
11769+
while let fieldNumber = try decoder.nextFieldNumber() {
11770+
// The use of inline closures is to circumvent an issue where the compiler
11771+
// allocates stack space for every case branch when no optimizations are
11772+
// enabled. https://github.com/apple/swift-protobuf/issues/1034
11773+
switch fieldNumber {
11774+
case 1: try { try decoder.decodeSingularEnumField(value: &self.returnType) }()
11775+
default: break
11776+
}
11777+
}
1176711778
}
1176811779

1176911780
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
11781+
if self.returnType != .consti32 {
11782+
try visitor.visitSingularEnumField(value: self.returnType, fieldNumber: 1)
11783+
}
1177011784
try unknownFields.traverse(visitor: &visitor)
1177111785
}
1177211786

1177311787
public static func ==(lhs: Fuzzilli_Protobuf_WasmEndLoop, rhs: Fuzzilli_Protobuf_WasmEndLoop) -> Bool {
11788+
if lhs.returnType != rhs.returnType {return false}
1177411789
if lhs.unknownFields != rhs.unknownFields {return false}
1177511790
return true
1177611791
}

Sources/Fuzzilli/Protobuf/operations.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,7 @@ message WasmBeginLoop {
11441144
}
11451145

11461146
message WasmEndLoop {
1147+
WasmILType returnType = 1;
11471148
}
11481149

11491150
message WasmBeginTry {

Tests/FuzzilliTests/WasmTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,33 @@ class WasmFoundationTests: XCTestCase {
753753
testForOutput(program: jsProg, runner: runner, outputString: "1368\n")
754754
}
755755

756+
func testLoopWithParametersAndResult() throws {
757+
let runner = try GetJavaScriptExecutorOrSkipTest()
758+
let liveTestConfig = Configuration(logLevel: .error, enableInspection: true)
759+
let fuzzer = makeMockFuzzer(config: liveTestConfig, environment: JavaScriptEnvironment())
760+
let b = fuzzer.makeBuilder()
761+
let outputFunc = b.createNamedVariable(forBuiltin: "output")
762+
let module = b.buildWasmModule { wasmModule in
763+
wasmModule.addWasmFunction(with: [.wasmi32, .wasmi32] => .wasmi32) { function, args in
764+
let loopResult = function.wasmBuildLoop(with: [.wasmi32, .wasmi32] => .wasmi32, args: args) { loopLabel, loopArgs in
765+
let incFirst = function.wasmi32BinOp(loopArgs[0], function.consti32(1), binOpKind: .Add)
766+
let incSecond = function.wasmi32BinOp(loopArgs[1], function.consti32(2), binOpKind: .Add)
767+
let condition = function.wasmi32CompareOp(incFirst, incSecond, using: .Gt_s)
768+
function.wasmBranchIf(condition, to: loopLabel, args: [incFirst, incSecond])
769+
return incFirst
770+
}
771+
function.wasmReturn(loopResult)
772+
}
773+
}
774+
let exports = module.loadExports()
775+
let wasmOut = b.callMethod(module.getExportedMethod(at: 0), on: exports, withArgs: [b.loadInt(10), b.loadInt(0)])
776+
b.callFunction(outputFunc, withArgs: [b.callMethod("toString", on: wasmOut)])
777+
778+
let prog = b.finalize()
779+
let jsProg = fuzzer.lifter.lift(prog)
780+
testForOutput(program: jsProg, runner: runner, outputString: "20\n")
781+
}
782+
756783
func testIfs() throws {
757784
let runner = try GetJavaScriptExecutorOrSkipTest()
758785
let liveTestConfig = Configuration(logLevel: .error, enableInspection: true)

0 commit comments

Comments
 (0)