Skip to content

Commit 9fd0a89

Browse files
LiedtkeV8-internal LUCI CQ
authored andcommitted
[wasm] Add support for blocks with result
This changes wasm blocks (only the "plain" block, not ifs, trys etc.) to have an optional result type. A wasm block with a result produces a Variable in its WasmEndBlock operation. The value is either the input argument to the WasmEndBlock (in case of regularly reaching the end of the block) or the arguments passed into a WasmBranch or WasmBranchIf operation that branches to the WasmBeginBlock's label. As these branch instructions need to provide the argument(s) matching the block signature, the label type is extended to also contain type information about the arguments to be passed along a branch. Change-Id: I4a1af782330c7a71320a2d4b1949dba081d1d4de Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/7967831 Reviewed-by: Carl Smith <[email protected]> Commit-Queue: Matthias Liedtke <[email protected]>
1 parent f6d29eb commit 9fd0a89

File tree

10 files changed

+253
-79
lines changed

10 files changed

+253
-79
lines changed

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3236,21 +3236,30 @@ public class ProgramBuilder {
32363236
// TODO(cffsmith): I think the best way to handle these types of blocks is to treat them like inline functions that have a signature. E.g. they behave like a definition and call of a wasmfunction. The output should be the output of the signature.
32373237
public func wasmBuildBlock(with signature: Signature, args: [Variable], body: (Variable, [Variable]) -> ()) {
32383238
assert(signature.parameters.count == args.count)
3239+
assert(signature.outputType == .nothing)
32393240
let instr = b.emit(WasmBeginBlock(with: signature), withInputs: args)
3240-
b.setType(ofVariable: instr.innerOutput(0), to: .label)
32413241
body(instr.innerOutput(0), Array(instr.innerOutputs(1...)))
3242-
b.emit(WasmEndBlock())
3242+
b.emit(WasmEndBlock(outputType: signature.outputType))
3243+
}
3244+
3245+
@discardableResult
3246+
public func wasmBuildBlockWithResult(with signature: Signature, args: [Variable], body: (Variable, [Variable]) -> Variable) -> Variable {
3247+
assert(signature.parameters.count == args.count)
3248+
assert(signature.outputType != .nothing)
3249+
let instr = b.emit(WasmBeginBlock(with: signature), withInputs: args)
3250+
let result = body(instr.innerOutput(0), Array(instr.innerOutputs(1...)))
3251+
return b.emit(WasmEndBlock(outputType: signature.outputType), withInputs: [result]).output
32433252
}
32443253

32453254
// This can branch to label variables only, has a variable input for dataflow purposes.
3246-
public func wasmBranch(to label: Variable) {
3247-
assert(b.type(of: label) == .label)
3248-
b.emit(WasmBranch(), withInputs: [label])
3255+
public func wasmBranch(to label: Variable, args: [Variable] = []) {
3256+
assert(b.type(of: label).Is(.anyLabel))
3257+
b.emit(WasmBranch(labelTypes: b.type(of: label).wasmLabelType!.parameters), withInputs: [label] + args)
32493258
}
32503259

3251-
public func wasmBranchIf(_ condition: Variable, to label: Variable) {
3252-
assert(b.type(of: label) == .label)
3253-
b.emit(WasmBranchIf(), withInputs: [label, condition])
3260+
public func wasmBranchIf(_ condition: Variable, to label: Variable, args: [Variable] = []) {
3261+
assert(b.type(of: label).Is(.label(args.map({b.type(of: $0)}))), "label type \(b.type(of: label)) doesn't match argument types \(args.map({b.type(of: $0)}))")
3262+
b.emit(WasmBranchIf(labelTypes: b.type(of: label).wasmLabelType!.parameters), withInputs: [label] + args + [condition])
32543263
}
32553264

32563265
public func wasmBuildIfElse(_ condition: Variable, ifBody: () -> Void, elseBody: () -> Void) {

Sources/Fuzzilli/CodeGen/WasmCodeGenerators.swift

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -560,10 +560,17 @@ 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): Also add support for return values (and probably find a suitable mechanism that we can also use for functions).
564-
// A good solution would probably be to make the endBlock take the return type as an input.
565-
function.wasmBuildBlock(with: parameters => .nothing, args: args) { label, args in
566-
b.buildRecursive()
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])
565+
if outputType != .nothing {
566+
function.wasmBuildBlockWithResult(with: parameters => outputType, args: args) { label, args in
567+
b.buildRecursive()
568+
return b.randomVariable(ofType: outputType) ?? function.generateRandomWasmVar(ofType: outputType)
569+
}
570+
} else {
571+
function.wasmBuildBlock(with: parameters => outputType, args: args) { label, args in
572+
b.buildRecursive()
573+
}
567574
}
568575
},
569576

@@ -603,7 +610,7 @@ public let WasmCodeGenerators: [CodeGenerator] = [
603610
}
604611
},
605612

606-
RecursiveCodeGenerator("WasmLegacyTryDelegateGenerator", inContext: .wasmFunction, inputs: .required(.label)) { b, label in
613+
RecursiveCodeGenerator("WasmLegacyTryDelegateGenerator", inContext: .wasmFunction, inputs: .required(.anyLabel)) { b, label in
607614
let function = b.currentWasmModule.currentWasmFunction
608615
// Choose a few random wasm values as arguments if available.
609616
let args = (0..<5).map {_ in b.findVariable {b.type(of: $0).Is(.wasmPrimitive)}}.filter {$0 != nil}.map {$0!}
@@ -687,14 +694,20 @@ public let WasmCodeGenerators: [CodeGenerator] = [
687694
b.currentWasmModule.addTag(parameterTypes: b.randomTagParameters())
688695
},
689696

690-
CodeGenerator("WasmBranchGenerator", inContext: .wasmFunction, inputs: .required(.label)) { b, label in
697+
CodeGenerator("WasmBranchGenerator", inContext: .wasmFunction, inputs: .required(.anyLabel)) { b, label in
691698
let function = b.currentWasmModule.currentWasmFunction
692-
function.wasmBranch(to: label)
699+
let args = b.type(of: label).wasmLabelType!.parameters.map {
700+
b.randomVariable(ofType: $0) ?? function.generateRandomWasmVar(ofType: $0)
701+
}
702+
function.wasmBranch(to: label, args: args)
693703
},
694704

695-
CodeGenerator("WasmBranchIfGenerator", inContext: .wasmFunction, inputs: .required(.label, .wasmi32)) { b, label, conditionVar in
705+
CodeGenerator("WasmBranchIfGenerator", inContext: .wasmFunction, inputs: .required(.anyLabel, .wasmi32)) { b, label, conditionVar in
696706
let function = b.currentWasmModule.currentWasmFunction
697-
function.wasmBranchIf(conditionVar, to: label)
707+
let args = b.type(of: label).wasmLabelType!.parameters.map {
708+
b.randomVariable(ofType: $0) ?? function.generateRandomWasmVar(ofType: $0)
709+
}
710+
function.wasmBranchIf(conditionVar, to: label, args: args)
698711
},
699712

700713
CodeGenerator("ConstSimd128Generator", inContext: .wasmFunction) { b in

Sources/Fuzzilli/FuzzIL/Instruction.swift

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,8 +1194,10 @@ extension Instruction: ProtobufConvertible {
11941194
$0.parameters = convertParametersToWasmTypeEnums(op.signature.parameters)
11951195
$0.returnType = ILTypeToWasmTypeEnum(op.signature.outputType)
11961196
}
1197-
case .wasmEndBlock(_):
1198-
$0.wasmEndBlock = Fuzzilli_Protobuf_WasmEndBlock()
1197+
case .wasmEndBlock(let op):
1198+
$0.wasmEndBlock = Fuzzilli_Protobuf_WasmEndBlock.with {
1199+
$0.returnType = ILTypeToWasmTypeEnum(op.outputType)
1200+
}
11991201
case .wasmBeginLoop(let op):
12001202
$0.wasmBeginLoop = Fuzzilli_Protobuf_WasmBeginLoop.with {
12011203
$0.parameters = convertParametersToWasmTypeEnums(op.signature.parameters)
@@ -1238,10 +1240,14 @@ extension Instruction: ProtobufConvertible {
12381240
$0.wasmDefineTag = Fuzzilli_Protobuf_WasmDefineTag.with {
12391241
$0.parameters = convertParametersToWasmTypeEnums(op.parameters)
12401242
}
1241-
case .wasmBranch(_):
1242-
$0.wasmBranch = Fuzzilli_Protobuf_WasmBranch()
1243-
case .wasmBranchIf(_):
1244-
$0.wasmBranchIf = Fuzzilli_Protobuf_WasmBranchIf()
1243+
case .wasmBranch(let op):
1244+
$0.wasmBranch = Fuzzilli_Protobuf_WasmBranch.with {
1245+
$0.parameters = op.labelTypes.map(ILTypeToWasmTypeEnum)
1246+
}
1247+
case .wasmBranchIf(let op):
1248+
$0.wasmBranchIf = Fuzzilli_Protobuf_WasmBranchIf.with {
1249+
$0.parameters = op.labelTypes.map(ILTypeToWasmTypeEnum)
1250+
}
12451251
case .wasmBeginIf(let op):
12461252
$0.wasmBeginIf = Fuzzilli_Protobuf_WasmBeginIf.with {
12471253
$0.parameters = convertParametersToWasmTypeEnums(op.signature.parameters)
@@ -1991,8 +1997,8 @@ extension Instruction: ProtobufConvertible {
19911997
Parameter.plain(WasmTypeEnumToILType(param))
19921998
})
19931999
op = WasmBeginBlock(with: parameters => WasmTypeEnumToILType(p.returnType))
1994-
case .wasmEndBlock(_):
1995-
op = WasmEndBlock()
2000+
case .wasmEndBlock(let p):
2001+
op = WasmEndBlock(outputType: WasmTypeEnumToILType(p.returnType))
19962002
case .wasmBeginLoop(let p):
19972003
let parameters: [Parameter] = p.parameters.map({ param in
19982004
Parameter.plain(WasmTypeEnumToILType(param))
@@ -2033,10 +2039,10 @@ extension Instruction: ProtobufConvertible {
20332039
op = WasmDefineTag(parameters: p.parameters.map({ param in
20342040
Parameter.plain(WasmTypeEnumToILType(param))
20352041
}))
2036-
case .wasmBranch(_):
2037-
op = WasmBranch()
2038-
case .wasmBranchIf(_):
2039-
op = WasmBranchIf()
2042+
case .wasmBranch(let p):
2043+
op = WasmBranch(labelTypes: p.parameters.map(WasmTypeEnumToILType))
2044+
case .wasmBranchIf(let p):
2045+
op = WasmBranchIf(labelTypes: p.parameters.map(WasmTypeEnumToILType))
20402046
case .wasmBeginIf(let p):
20412047
let parameters: [Parameter] = p.parameters.map({ param in
20422048
Parameter.plain(WasmTypeEnumToILType(param))

Sources/Fuzzilli/FuzzIL/TypeSystem.swift

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,11 @@ public struct ILType: Hashable {
175175
// Internal types
176176

177177
// This type is used to indicate block labels in wasm.
178-
public static let label: ILType = ILType(definiteType: .label)
178+
public static func label(_ parameterTypes: [ILType] = []) -> ILType {
179+
return ILType(definiteType: .label, ext: TypeExtension(group: "WasmLabel", properties: [], methods: [], signature: nil, wasmExt: WasmLabelType(parameterTypes)))
180+
}
181+
182+
public static let anyLabel: ILType = ILType(definiteType: .label, ext: TypeExtension(group: "WasmLabel", properties: [], methods: [], signature: nil, wasmExt: nil))
179183

180184
/// A label that allows rethrowing the caught exception of a catch block.
181185
public static let exceptionLabel: ILType = ILType(definiteType: .exceptionLabel)
@@ -409,6 +413,14 @@ public struct ILType: Hashable {
409413
return wasmTagType != nil && ext?.group == "WasmTag"
410414
}
411415

416+
public var wasmLabelType: WasmLabelType? {
417+
return wasmType as? WasmLabelType
418+
}
419+
420+
public var isWasmLabelType: Bool {
421+
return wasmTagType != nil
422+
}
423+
412424
public var properties: Set<String> {
413425
return ext?.properties ?? Set()
414426
}
@@ -977,11 +989,12 @@ public class WasmTagType: WasmTypeExtension {
977989

978990
override func isEqual(to other: WasmTypeExtension) -> Bool {
979991
guard let other = other as? WasmTagType else { return false }
980-
return self.parameters == other.parameters
992+
return self.parameters == other.parameters && self.isJSTag == other.isJSTag
981993
}
982994

983995
override public func hash(into hasher: inout Hasher) {
984996
hasher.combine(parameters)
997+
hasher.combine(isJSTag)
985998
}
986999

9871000
init(_ parameters: ParameterList, isJSTag: Bool = false) {
@@ -990,6 +1003,27 @@ public class WasmTagType: WasmTypeExtension {
9901003
}
9911004
}
9921005

1006+
public class WasmLabelType: WasmTypeExtension {
1007+
// The parameter types for the label, meaning the types of the values that need to be provided
1008+
// when branching to this label. This is the list of result types for all wasm blocks excluding
1009+
// the loop for which the parameter types are the parameter types of the block. (This is caused
1010+
// by the branch instruction branching to the loop header and not the loop end.)
1011+
public let parameters: [ILType]
1012+
1013+
override func isEqual(to other: WasmTypeExtension) -> Bool {
1014+
guard let other = other as? WasmLabelType else { return false }
1015+
return self.parameters == other.parameters
1016+
}
1017+
1018+
override public func hash(into hasher: inout Hasher) {
1019+
hasher.combine(parameters)
1020+
}
1021+
1022+
init(_ parameters: [ILType]) {
1023+
self.parameters = parameters
1024+
}
1025+
}
1026+
9931027
public struct Limits: Hashable {
9941028
var min: Int
9951029
var max: Int?

Sources/Fuzzilli/FuzzIL/WasmOperations.swift

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -953,15 +953,16 @@ final class WasmBeginBlock: WasmOperation {
953953
init(with signature: Signature) {
954954
self.signature = signature
955955
let parameterTypes = signature.parameters.convertPlainToILTypes()
956-
super.init(inputTypes: parameterTypes, outputType: signature.outputType, innerOutputTypes: [.label] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock])
956+
let labelTypes = signature.outputType != .nothing ? [signature.outputType] : []
957+
super.init(inputTypes: parameterTypes, outputType: .nothing, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock])
957958
}
958959
}
959960

960961
final class WasmEndBlock: WasmOperation {
961962
override var opcode: Opcode { .wasmEndBlock(self) }
962963

963-
init() {
964-
super.init(attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction, .wasmBlock])
964+
init(outputType: ILType) {
965+
super.init(inputTypes: outputType != .nothing ? [outputType] : [], outputType: outputType, attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction, .wasmBlock])
965966
}
966967
}
967968

@@ -972,12 +973,13 @@ final class WasmBeginIf: WasmOperation {
972973
init(with signature: Signature = [] => .nothing) {
973974
self.signature = signature
974975
let parameterTypes = signature.parameters.convertPlainToILTypes()
976+
let labelTypes = signature.outputType != .nothing ? [signature.outputType] : []
975977
// TODO(mliedtke): Why does this set .isNotInputMutable? Try to remove it and see if the WasmLifter failure rate is affected.
976978

977979
// Note that the condition is the last input! This is due to how lifting works for the wasm
978980
// value stack and that the condition is the first value to be removed from the stack, so
979981
// it needs to be the last one pushed to it.
980-
super.init(inputTypes: parameterTypes + [.wasmi32], outputType: signature.outputType, innerOutputTypes: [.label] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext, .isNotInputMutable], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock])
982+
super.init(inputTypes: parameterTypes + [.wasmi32], outputType: signature.outputType, innerOutputTypes: [.label(labelTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext, .isNotInputMutable], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock])
981983
}
982984
}
983985

@@ -988,7 +990,8 @@ final class WasmBeginElse: WasmOperation {
988990
init(with signature: Signature = [] => .nothing) {
989991
self.signature = signature
990992
let parameterTypes = signature.parameters.convertPlainToILTypes()
991-
super.init(outputType: signature.outputType, innerOutputTypes: [.label] + parameterTypes, attributes: [.isBlockStart, .isBlockEnd, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmBlock])
993+
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])
992995
}
993996
}
994997

@@ -1008,7 +1011,10 @@ final class WasmBeginLoop: WasmOperation {
10081011
init(with signature: Signature) {
10091012
self.signature = signature
10101013
let parameterTypes = signature.parameters.convertPlainToILTypes()
1011-
super.init(outputType: signature.outputType, innerOutputTypes: [.label] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction])
1014+
// Note that different to all other blocks the loop's label parameters are the input types
1015+
// of the block, not the result types (because a branch to a loop label jumps to the
1016+
// beginning of the loop block instead of the end.)
1017+
super.init(outputType: .nothing, innerOutputTypes: [.label(parameterTypes)] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction])
10121018
}
10131019
}
10141020

@@ -1028,7 +1034,8 @@ final class WasmBeginTry: WasmOperation {
10281034
init(with signature: Signature) {
10291035
self.signature = signature
10301036
let parameterTypes = signature.parameters.convertPlainToILTypes()
1031-
super.init(inputTypes: parameterTypes, outputType: signature.outputType, innerOutputTypes: [.label] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [.wasmTry])
1037+
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])
10321039
}
10331040
}
10341041

@@ -1101,7 +1108,8 @@ final class WasmBeginTryDelegate: WasmOperation {
11011108
init(with signature: Signature) {
11021109
self.signature = signature
11031110
let parameterTypes = signature.parameters.convertPlainToILTypes()
1104-
super.init(inputTypes: parameterTypes, outputType: signature.outputType, innerOutputTypes: [.label] + parameterTypes, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction], contextOpened: [])
1111+
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: [])
11051113
}
11061114
}
11071115

@@ -1111,7 +1119,8 @@ final class WasmEndTryDelegate: WasmOperation {
11111119
override var opcode: Opcode { .wasmEndTryDelegate(self) }
11121120

11131121
init() {
1114-
super.init(inputTypes: [.label], attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction])
1122+
// Note that the actual block signature doesn't matter as the try-delegate "rethrows" the exception at that block level.
1123+
super.init(inputTypes: [.anyLabel], attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction])
11151124
}
11161125
}
11171126

@@ -1136,20 +1145,23 @@ final class WasmRethrow: WasmOperation {
11361145

11371146
final class WasmBranch: WasmOperation {
11381147
override var opcode: Opcode { .wasmBranch(self) }
1148+
let labelTypes: [ILType]
11391149

1140-
init() {
1141-
super.init(inputTypes: [.label], requiredContext: [.wasmFunction])
1150+
init(labelTypes: [ILType]) {
1151+
self.labelTypes = labelTypes
1152+
super.init(inputTypes: [.label(self.labelTypes)] + labelTypes, requiredContext: [.wasmFunction])
11421153

11431154
}
11441155
}
11451156

11461157
final class WasmBranchIf: WasmOperation {
11471158
override var opcode: Opcode { .wasmBranchIf(self) }
1159+
let labelTypes: [ILType]
11481160

1149-
init() {
1150-
super.init(inputTypes: [.label, .wasmi32], requiredContext: [.wasmFunction])
1161+
init(labelTypes: [ILType]) {
1162+
self.labelTypes = labelTypes
1163+
super.init(inputTypes: [.label(self.labelTypes)] + labelTypes + [.wasmi32], requiredContext: [.wasmFunction])
11511164
}
1152-
11531165
}
11541166

11551167
// TODO: make this comprehensive, currently only works for locals, or assumes every thing it reassigns to is a local.

0 commit comments

Comments
 (0)