Skip to content

Commit 92f9eb9

Browse files
LiedtkeV8-internal LUCI CQ
authored andcommitted
[wasm] Add support for if else blocks with result
Change-Id: Idd76862352cea3bbbbc62c4d0b4e4c3556245c04 Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/7967832 Reviewed-by: Carl Smith <[email protected]> Commit-Queue: Matthias Liedtke <[email protected]>
1 parent 9fd0a89 commit 92f9eb9

File tree

8 files changed

+109
-46
lines changed

8 files changed

+109
-46
lines changed

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3278,6 +3278,15 @@ public class ProgramBuilder {
32783278
b.emit(WasmEndIf())
32793279
}
32803280

3281+
@discardableResult
3282+
public func wasmBuildIfElseWithResult(_ condition: Variable, signature: Signature, args: [Variable], ifBody: (Variable, [Variable]) -> Variable, elseBody: (Variable, [Variable]) -> Variable) -> Variable{
3283+
let beginBlock = b.emit(WasmBeginIf(with: signature), withInputs: args + [condition])
3284+
let trueResult = ifBody(beginBlock.innerOutput(0), Array(beginBlock.innerOutputs(1...)))
3285+
let elseBlock = b.emit(WasmBeginElse(with: signature), withInputs: [trueResult])
3286+
let falseResult = elseBody(elseBlock.innerOutput(0), Array(elseBlock.innerOutputs(1...)))
3287+
return b.emit(WasmEndIf(outputType: signature.outputType), withInputs: [falseResult]).output
3288+
}
3289+
32813290
// The first output of this block is a label variable, which is just there to explicitly mark control-flow and allow branches.
32823291
public func wasmBuildLoop(with signature: Signature, body: (Variable, [Variable]) -> Void) {
32833292
let instr = b.emit(WasmBeginLoop(with: signature))
@@ -3558,6 +3567,11 @@ public class ProgramBuilder {
35583567
return params => returnType
35593568
}
35603569

3570+
public func randomWasmBlockOutputType() -> ILType {
3571+
// 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])
3573+
}
3574+
35613575
@discardableResult
35623576
public func buildWasmModule(_ body: (WasmModule) -> ()) -> WasmModule {
35633577
emit(BeginWasmModule())

Sources/Fuzzilli/CodeGen/WasmCodeGenerators.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -560,8 +560,7 @@ public let WasmCodeGenerators: [CodeGenerator] = [
560560
// Choose a few random wasm values as arguments if available.
561561
let args = (0..<5).map {_ in b.findVariable {b.type(of: $0).Is(.wasmPrimitive)}}.filter {$0 != nil}.map {$0!}
562562
let parameters = args.map {arg in Parameter.plain(b.type(of: arg))}
563-
// TODO(mliedtke): The selection of types is in sync with ProgramBuilder::randomWasmSignature(). This should allow more types.
564-
let outputType: ILType = chooseUniform(from: [.wasmi32, .wasmi64, .wasmf32, .wasmf64, .nothing])
563+
let outputType = b.randomWasmBlockOutputType()
565564
if outputType != .nothing {
566565
function.wasmBuildBlockWithResult(with: parameters => outputType, args: args) { label, args in
567566
b.buildRecursive()
@@ -645,10 +644,21 @@ public let WasmCodeGenerators: [CodeGenerator] = [
645644
// Choose a few random wasm values as arguments if available.
646645
let args = (0..<5).map {_ in b.findVariable {b.type(of: $0).Is(.wasmPrimitive)}}.filter {$0 != nil}.map {$0!}
647646
let parameters = args.map {arg in Parameter.plain(b.type(of: arg))}
648-
function.wasmBuildIfElse(conditionVar, signature: parameters => .nothing, args: args) { label, args in
649-
b.buildRecursive(block: 1, of: 2, n: 4)
650-
} elseBody: { label, args in
651-
b.buildRecursive(block: 2, of: 2, n: 4)
647+
let outputType = b.randomWasmBlockOutputType()
648+
if outputType != .nothing {
649+
function.wasmBuildIfElseWithResult(conditionVar, signature: parameters => outputType, args: args) { label, args in
650+
b.buildRecursive(block: 1, of: 2, n: 4)
651+
return b.randomVariable(ofType: outputType) ?? function.generateRandomWasmVar(ofType: outputType)
652+
} elseBody: { label, args in
653+
b.buildRecursive(block: 2, of: 2, n: 4)
654+
return b.randomVariable(ofType: outputType) ?? function.generateRandomWasmVar(ofType: outputType)
655+
}
656+
} else {
657+
function.wasmBuildIfElse(conditionVar, signature: parameters => outputType, args: args) { label, args in
658+
b.buildRecursive(block: 1, of: 2, n: 4)
659+
} elseBody: { label, args in
660+
b.buildRecursive(block: 2, of: 2, n: 4)
661+
}
652662
}
653663
},
654664

Sources/Fuzzilli/FuzzIL/Instruction.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,8 +1258,10 @@ extension Instruction: ProtobufConvertible {
12581258
$0.parameters = convertParametersToWasmTypeEnums(op.signature.parameters)
12591259
$0.returnType = ILTypeToWasmTypeEnum(op.signature.outputType)
12601260
}
1261-
case .wasmEndIf(_):
1262-
$0.wasmEndIf = Fuzzilli_Protobuf_WasmEndIf()
1261+
case .wasmEndIf(let op):
1262+
$0.wasmEndIf = Fuzzilli_Protobuf_WasmEndIf.with {
1263+
$0.returnType = ILTypeToWasmTypeEnum(op.outputType)
1264+
}
12631265
case .wasmNop(_):
12641266
fatalError("Should never be serialized")
12651267
case .wasmUnreachable(_):
@@ -2053,8 +2055,8 @@ extension Instruction: ProtobufConvertible {
20532055
Parameter.plain(WasmTypeEnumToILType(param))
20542056
})
20552057
op = WasmBeginElse(with: parameters => WasmTypeEnumToILType(p.returnType))
2056-
case .wasmEndIf(_):
2057-
op = WasmEndIf()
2058+
case .wasmEndIf(let p):
2059+
op = WasmEndIf(outputType: WasmTypeEnumToILType(p.returnType))
20582060
case .wasmNop(_):
20592061
fatalError("Should never be deserialized!")
20602062
case .wasmUnreachable(_):

Sources/Fuzzilli/FuzzIL/WasmOperations.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ final class WasmBeginIf: WasmOperation {
979979
// Note that the condition is the last input! This is due to how lifting works for the wasm
980980
// value stack and that the condition is the first value to be removed from the stack, so
981981
// it needs to be the last one pushed to it.
982-
super.init(inputTypes: parameterTypes + [.wasmi32], outputType: signature.outputType, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext, .isNotInputMutable], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock])
982+
super.init(inputTypes: parameterTypes + [.wasmi32], outputType: .nothing, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext, .isNotInputMutable], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock])
983983
}
984984
}
985985

@@ -991,15 +991,19 @@ final class WasmBeginElse: WasmOperation {
991991
self.signature = signature
992992
let parameterTypes = signature.parameters.convertPlainToILTypes()
993993
let labelTypes = signature.outputType != .nothing ? [signature.outputType] : []
994-
super.init(outputType: signature.outputType, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .isBlockEnd, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock])
994+
// The WasmBeginElse acts both as a block end for the true case and as a block start for the
995+
// false case. As such, its input types are the results from the true block and its inner
996+
// output types are the same as for the corresponding WasmBeginIf.
997+
super.init(inputTypes: labelTypes, outputType: .nothing, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .isBlockEnd, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock])
995998
}
996999
}
9971000

9981001
final class WasmEndIf: WasmOperation {
9991002
override var opcode: Opcode { .wasmEndIf(self) }
10001003

1001-
init() {
1002-
super.init(attributes: [.isBlockEnd], requiredContext: [.wasmBlock, .wasmFunction])
1004+
init(outputType: ILType = .nothing) {
1005+
let inputTypes = outputType != .nothing ? [outputType] : []
1006+
super.init(inputTypes: inputTypes, outputType: outputType, attributes: [.isBlockEnd], requiredContext: [.wasmBlock, .wasmFunction])
10031007
}
10041008
}
10051009

@@ -1035,7 +1039,7 @@ final class WasmBeginTry: WasmOperation {
10351039
self.signature = signature
10361040
let parameterTypes = signature.parameters.convertPlainToILTypes()
10371041
let labelTypes = signature.outputType != .nothing ? [signature.outputType] : []
1038-
super.init(inputTypes: parameterTypes, outputType: signature.outputType, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmTry])
1042+
super.init(inputTypes: parameterTypes, outputType: .nothing, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmTry])
10391043
}
10401044
}
10411045

@@ -1109,7 +1113,7 @@ final class WasmBeginTryDelegate: WasmOperation {
11091113
self.signature = signature
11101114
let parameterTypes = signature.parameters.convertPlainToILTypes()
11111115
let labelTypes = signature.outputType != .nothing ? [signature.outputType] : []
1112-
super.init(inputTypes: parameterTypes, outputType: signature.outputType, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [])
1116+
super.init(inputTypes: parameterTypes, outputType: .nothing, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [])
11131117
}
11141118
}
11151119

Sources/Fuzzilli/Lifting/FuzzILLifter.swift

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ public class FuzzILLifter: Lifter {
797797

798798
case .beginWasmFunction(let op):
799799
// TODO(cffsmith): do this properly?
800-
w.emit("BeginWasmFunction [\(liftCallArguments(instr.innerOutputs))] (\(op.signature))")
800+
w.emit("BeginWasmFunction (\(op.signature)) -> [\(liftCallArguments(instr.innerOutputs))]")
801801
w.increaseIndentionLevel()
802802

803803
case .endWasmFunction:
@@ -982,24 +982,15 @@ public class FuzzILLifter: Lifter {
982982
}
983983

984984
case .wasmBeginLoop(let op):
985-
if instr.numOutputs > 0 {
986-
w.emit("\(output()) <- WasmBeginLoop L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
987-
} else {
988-
w.emit("WasmBeginLoop L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
989-
}
985+
w.emit("WasmBeginLoop L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
990986
w.increaseIndentionLevel()
991987

992988
case .wasmEndLoop(_):
993989
w.decreaseIndentionLevel()
994990
w.emit("WasmEndLoop")
995991

996992
case .wasmBeginTry(let op):
997-
if instr.numOutputs > 0 {
998-
// TODO(cffsmith): Maybe lift labels as e.g. L7 or something like that?
999-
w.emit("\(output()) <- WasmBeginTry L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
1000-
} else {
1001-
w.emit("WasmBeginTry L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
1002-
}
993+
w.emit("WasmBeginTry L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
1003994
w.increaseIndentionLevel()
1004995

1005996
case .wasmBeginCatchAll(_):
@@ -1027,12 +1018,7 @@ public class FuzzILLifter: Lifter {
10271018
w.emit("WasmRethrow \(instr.input(0))")
10281019

10291020
case .wasmBeginTryDelegate(let op):
1030-
if instr.numOutputs > 0 {
1031-
// TODO(cffsmith): Maybe lift labels as e.g. L7 or something like that?
1032-
w.emit("\(output()) <- WasmBeginTryDelegate L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
1033-
} else {
1034-
w.emit("WasmBeginTryDelegate L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
1035-
}
1021+
w.emit("WasmBeginTryDelegate L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
10361022
w.increaseIndentionLevel()
10371023

10381024
case .wasmEndTryDelegate(_):
@@ -1050,21 +1036,25 @@ public class FuzzILLifter: Lifter {
10501036

10511037
case .wasmBeginIf(let op):
10521038
let inputs = instr.inputs.map(lift).joined(separator: ", ")
1053-
if instr.numOutputs > 0 {
1054-
w.emit("\(output()) <- wasmBeginIf L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature)) [\(inputs)]")
1055-
} else {
1056-
w.emit("wasmBeginIf L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature)) [\(inputs)]")
1057-
}
1039+
w.emit("wasmBeginIf (\(op.signature)) [\(inputs)] -> L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))]")
10581040
w.increaseIndentionLevel()
10591041

1060-
case .wasmBeginElse(let op):
1042+
case .wasmBeginElse(_):
10611043
w.decreaseIndentionLevel()
1062-
w.emit("wasmBeginElse L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))] (\(op.signature))")
1044+
let inputs = instr.inputs.map(lift).joined(separator: ", ")
1045+
// Note that the signature is printed by the WasmBeginIf, so we skip it here for better
1046+
// readability.
1047+
w.emit("wasmBeginElse [\(inputs)] -> L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))]")
10631048
w.increaseIndentionLevel()
10641049

1065-
case .wasmEndIf(_):
1050+
case .wasmEndIf(let op):
10661051
w.decreaseIndentionLevel()
1067-
w.emit("wasmEndIf")
1052+
let inputs = instr.inputs.map(lift).joined(separator: ", ")
1053+
if op.numOutputs > 0 {
1054+
w.emit("\(output()) <- WasmEndIf \(inputs)")
1055+
} else {
1056+
w.emit("WasmEndIf \(inputs)")
1057+
}
10681058

10691059
case .print:
10701060
w.emit("Print \(input(0))")

Sources/Fuzzilli/Protobuf/operations.pb.swift

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

4177+
public var returnType: Fuzzilli_Protobuf_WasmILType = .consti32
4178+
41774179
public var unknownFields = SwiftProtobuf.UnknownStorage()
41784180

41794181
public init() {}
@@ -12234,18 +12236,31 @@ extension Fuzzilli_Protobuf_WasmBeginElse: SwiftProtobuf.Message, SwiftProtobuf.
1223412236

1223512237
extension Fuzzilli_Protobuf_WasmEndIf: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
1223612238
public static let protoMessageName: String = _protobuf_package + ".WasmEndIf"
12237-
public static let _protobuf_nameMap = SwiftProtobuf._NameMap()
12239+
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
12240+
1: .same(proto: "returnType"),
12241+
]
1223812242

1223912243
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
12240-
// Load everything into unknown fields
12241-
while try decoder.nextFieldNumber() != nil {}
12244+
while let fieldNumber = try decoder.nextFieldNumber() {
12245+
// The use of inline closures is to circumvent an issue where the compiler
12246+
// allocates stack space for every case branch when no optimizations are
12247+
// enabled. https://github.com/apple/swift-protobuf/issues/1034
12248+
switch fieldNumber {
12249+
case 1: try { try decoder.decodeSingularEnumField(value: &self.returnType) }()
12250+
default: break
12251+
}
12252+
}
1224212253
}
1224312254

1224412255
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
12256+
if self.returnType != .consti32 {
12257+
try visitor.visitSingularEnumField(value: self.returnType, fieldNumber: 1)
12258+
}
1224512259
try unknownFields.traverse(visitor: &visitor)
1224612260
}
1224712261

1224812262
public static func ==(lhs: Fuzzilli_Protobuf_WasmEndIf, rhs: Fuzzilli_Protobuf_WasmEndIf) -> Bool {
12263+
if lhs.returnType != rhs.returnType {return false}
1224912264
if lhs.unknownFields != rhs.unknownFields {return false}
1225012265
return true
1225112266
}

Sources/Fuzzilli/Protobuf/operations.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,7 @@ message WasmBeginElse {
12091209
}
12101210

12111211
message WasmEndIf {
1212+
WasmILType returnType = 1;
12121213
}
12131214

12141215
message WasmNop {

Tests/FuzzilliTests/WasmTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,33 @@ class WasmFoundationTests: XCTestCase {
860860
testForOutput(program: jsProg, runner: runner, outputString: "100\n300\n200\n300\n")
861861
}
862862

863+
func testIfElseWithResult() throws {
864+
let runner = try GetJavaScriptExecutorOrSkipTest()
865+
let liveTestConfig = Configuration(logLevel: .error, enableInspection: true)
866+
let fuzzer = makeMockFuzzer(config: liveTestConfig, environment: JavaScriptEnvironment())
867+
let b = fuzzer.makeBuilder()
868+
let outputFunc = b.createNamedVariable(forBuiltin: "output")
869+
let module = b.buildWasmModule { wasmModule in
870+
wasmModule.addWasmFunction(with: [.wasmi32] => .wasmi64) { function, args in
871+
let blockResult = function.wasmBuildIfElseWithResult(args[0], signature: [] => .wasmi64, args: []) {label, args in
872+
return function.consti64(123)
873+
} elseBody: {label, args in
874+
return function.consti64(321)
875+
}
876+
function.wasmReturn(blockResult)
877+
}
878+
}
879+
let exports = module.loadExports()
880+
let outTrue = b.callMethod(module.getExportedMethod(at: 0), on: exports, withArgs: [b.loadInt(1)])
881+
b.callFunction(outputFunc, withArgs: [b.callMethod("toString", on: outTrue)])
882+
let outFalse = b.callMethod(module.getExportedMethod(at: 0), on: exports, withArgs: [b.loadInt(0)])
883+
b.callFunction(outputFunc, withArgs: [b.callMethod("toString", on: outFalse)])
884+
885+
let prog = b.finalize()
886+
let jsProg = fuzzer.lifter.lift(prog)
887+
testForOutput(program: jsProg, runner: runner, outputString: "123\n321\n")
888+
}
889+
863890
func testTryVoid() throws {
864891
let runner = try GetJavaScriptExecutorOrSkipTest()
865892
let liveTestConfig = Configuration(logLevel: .error, enableInspection: true)

0 commit comments

Comments
 (0)