Skip to content

Commit 3b241b0

Browse files
LiedtkeV8-internal LUCI CQ
authored andcommitted
[wasm] Change loops to use wasm-gc signatures
This allows using parameter types which are indexed types (things like `(ref null 1)`). Implementation: - Each WasmLoop instruction now takes its signature as the first input. - The static signature types are removed from the begin and endLoop. - The loop code generator emits an "ad hoc" signature in order to emit signatures for which we already have corresponding inputs available. Bug: 445356784 Change-Id: Ic58ab7d6a092a39de77c974142dd7f976786e8e1 Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/8792956 Reviewed-by: Pawel Krawczyk <pawkra@google.com> Commit-Queue: Matthias Liedtke <mliedtke@google.com>
1 parent 9c370c3 commit 3b241b0

13 files changed

Lines changed: 241 additions & 102 deletions

File tree

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,10 @@ public class ProgramBuilder {
13451345
jsTyper.setType(of: variable, to: variableType)
13461346
}
13471347

1348+
public func getWasmTypeDef(for type: ILType) -> Variable {
1349+
jsTyper.getWasmTypeDef(for: type)
1350+
}
1351+
13481352
/// This helper function converts parameter types into argument types, for example by "unrolling" rest parameters and handling optional parameters.
13491353
private static func prepareArgumentTypes(forParameters params: ParameterList) -> [ILType] {
13501354
var argumentTypes = [ILType]()
@@ -3998,18 +4002,25 @@ public class ProgramBuilder {
39984002
return Array(b.emit(WasmEndIf(outputTypes: signature.outputTypes), withInputs: falseResults, types: signature.outputTypes).outputs)
39994003
}
40004004

4001-
// The first output of this block is a label variable, which is just there to explicitly mark control-flow and allow branches.
4002-
public func wasmBuildLoop(with signature: WasmSignature, body: (Variable, [Variable]) -> Void) {
4003-
let instr = b.emit(WasmBeginLoop(with: signature))
4004-
body(instr.innerOutput(0), Array(instr.innerOutputs(1...)))
4005-
b.emit(WasmEndLoop())
4005+
@discardableResult
4006+
public func wasmBuildLoop(with signature: WasmSignature, args: [Variable], body: (Variable, [Variable]) -> [Variable]) -> [Variable] {
4007+
let signatureDef = b.wasmDefineAdHocSignatureType(signature: signature)
4008+
return wasmBuildLoopImpl(signature: signature, signatureDef: signatureDef, args: args, body: body)
40064009
}
40074010

40084011
@discardableResult
4009-
public func wasmBuildLoop(with signature: WasmSignature, args: [Variable], body: (Variable, [Variable]) -> [Variable]) -> [Variable] {
4010-
let instr = b.emit(WasmBeginLoop(with: signature), withInputs: args, types: signature.parameterTypes)
4012+
public func wasmBuildLoop(with signatureDef: Variable, args: [Variable], body: (Variable, [Variable]) -> [Variable]) -> [Variable] {
4013+
let signature = b.type(of: signatureDef).wasmFunctionSignatureDefSignature
4014+
return wasmBuildLoopImpl(signature: signature, signatureDef: signatureDef, args: args, body: body)
4015+
}
4016+
4017+
private func wasmBuildLoopImpl(signature: WasmSignature, signatureDef: Variable, args: [Variable], body: (Variable, [Variable]) -> [Variable]) -> [Variable] {
4018+
let instr = b.emit(WasmBeginLoop(with: signature), withInputs: [signatureDef] + args,
4019+
types: [.wasmTypeDef()] + signature.parameterTypes)
40114020
let fallthroughResults = body(instr.innerOutput(0), Array(instr.innerOutputs(1...)))
4012-
return Array(b.emit(WasmEndLoop(outputTypes: signature.outputTypes), withInputs: fallthroughResults, types: signature.outputTypes).outputs)
4021+
return Array(b.emit(WasmEndLoop(outputCount: signature.outputTypes.count),
4022+
withInputs: [signatureDef] + fallthroughResults,
4023+
types: [.wasmTypeDef()] + signature.outputTypes).outputs)
40134024
}
40144025

40154026
@discardableResult
@@ -4637,8 +4648,20 @@ public class ProgramBuilder {
46374648

46384649
/// Like wasmDefineSignatureType but instead of within a type group this defines a signature
46394650
/// type directly inside a wasm function.
4651+
/// This takes a signature with resolved index types for ease-of-use (meaning it accepts full
4652+
/// index reference types directly inside the signature).
46404653
@discardableResult
4641-
func wasmDefineAdHocSignatureType(signature: WasmSignature, indexTypes: [Variable]) -> Variable {
4654+
func wasmDefineAdHocSignatureType(signature: WasmSignature) -> Variable {
4655+
let indexTypes = (signature.parameterTypes + signature.outputTypes)
4656+
.filter {$0.Is(.anyIndexRef)}
4657+
.map(getWasmTypeDef)
4658+
let cleanIndexTypes = {(type: ILType) -> ILType in
4659+
type.Is(.anyIndexRef)
4660+
? .wasmRef(.Index(), nullability: type.wasmReferenceType!.nullability)
4661+
: type
4662+
}
4663+
let signature = signature.parameterTypes.map(cleanIndexTypes)
4664+
=> signature.outputTypes.map(cleanIndexTypes)
46424665
return emit(WasmDefineAdHocSignatureType(signature: signature), withInputs: indexTypes).output
46434666
}
46444667

@@ -4854,16 +4877,13 @@ public class ProgramBuilder {
48544877
activeWasmModule!.blockSignatures.push(op.signature)
48554878
case .wasmBeginBlock(let op):
48564879
activeWasmModule!.blockSignatures.push(op.signature)
4857-
case .wasmBeginLoop(let op):
4858-
activeWasmModule!.blockSignatures.push(op.signature)
48594880
case .wasmBeginTry(let op):
48604881
activeWasmModule!.blockSignatures.push(op.signature)
48614882
case .wasmBeginTryDelegate(let op):
48624883
activeWasmModule!.blockSignatures.push(op.signature)
48634884
case .wasmBeginTryTable(let op):
48644885
activeWasmModule!.blockSignatures.push(op.signature)
48654886
case .wasmEndIf(_),
4866-
.wasmEndLoop(_),
48674887
.wasmEndTry(_),
48684888
.wasmEndTryDelegate(_),
48694889
.wasmEndTryTable(_),

Sources/Fuzzilli/CodeGen/WasmCodeGenerators.swift

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,7 +1261,9 @@ public let WasmCodeGenerators: [CodeGenerator] = [
12611261
let function = b.currentWasmModule.currentWasmFunction
12621262
let loopCtr = function.consti32(10)
12631263
b.runtimeData.push("loopCounter", loopCtr)
1264-
b.emit(WasmBeginLoop(with: [] => []))
1264+
let signature = b.wasmDefineAdHocSignatureType(signature: [] => [])
1265+
b.runtimeData.push("loopSignature", signature)
1266+
b.emit(WasmBeginLoop(with: [] => []), withInputs: [signature])
12651267
// Increase loop counter.
12661268
let result = function.wasmi32BinOp(
12671269
loopCtr, function.consti32(1), binOpKind: .Sub)
@@ -1275,38 +1277,59 @@ public let WasmCodeGenerators: [CodeGenerator] = [
12751277
let loopCtr = b.runtimeData.pop("loopCounter")
12761278
// Backedge of loop, we continue if it is not equal to zero.
12771279
let isNotZero = function.wasmi32CompareOp(loopCtr, function.consti32(0), using: .Ne)
1278-
b.emit(WasmEndLoop(outputTypes: []))
1280+
b.emit(WasmEndLoop(outputCount: 0), withInputs: [b.runtimeData.pop("loopSignature")])
12791281
},
12801282
]),
12811283

1282-
CodeGenerator("WasmLoopWithSignatureGenerator", inContext: .single(.wasmFunction)) {
1283-
b in
1284-
let function = b.currentWasmModule.currentWasmFunction
1285-
// Count upwards here to make it slightly more different from the other loop generator.
1286-
// Also, instead of using reassign, this generator uses the signature to pass and update the loop counter.
1287-
let randomArgs = b.randomWasmBlockArguments(upTo: 5)
1288-
let randomArgTypes = randomArgs.map { b.type(of: $0) }
1289-
let args = [function.consti32(0)] + randomArgs
1290-
let parameters = args.map(b.type)
1291-
let outputTypes = b.randomWasmBlockOutputTypes(upTo: 5)
1292-
// Note that due to the do-while style implementation, the actual iteration count is at least 1.
1293-
let iterationCount = Int32.random(in: 0...16)
1284+
CodeGenerator("WasmLoopWithSignatureGenerator", [
1285+
GeneratorStub("WasmBeginLoopWithSignatureGenerator", inContext: .single(.wasmFunction),
1286+
provides: [.wasmFunction]) { b in
1287+
let function = b.currentWasmModule.currentWasmFunction
1288+
// Count upwards here to make it slightly more different from the other loop generator.
1289+
// Also, instead of using reassign, this generator uses the signature to pass and update the loop counter.
1290+
let randomArgs = b.randomWasmBlockArguments(upTo: 5)
1291+
let randomArgTypes = randomArgs.map { b.type(of: $0) }
1292+
let args = [function.consti32(0)] + randomArgs
1293+
let parameters = args.map(b.type)
1294+
// TODO(mliedtke): Also allow index types in the output types.
1295+
let outputTypes = b.randomWasmBlockOutputTypes(upTo: 5)
1296+
// Calculate index types for the dynamically created signature.
1297+
let indexTypes = (parameters + outputTypes)
1298+
.filter {$0.Is(.anyIndexRef)}
1299+
.map(b.getWasmTypeDef)
1300+
1301+
// TODO(mliedtke): We need to cleanup the index types here!
1302+
let signature = b.wasmDefineAdHocSignatureType(signature: parameters => outputTypes)
1303+
let loopBegin = b.emit(WasmBeginLoop(parameterCount: parameters.count),
1304+
withInputs: [signature] + args)
1305+
let loopCounter = loopBegin.innerOutput(1)
1306+
assert(b.type(of: loopCounter).Is(.wasmi32))
1307+
b.runtimeData.push("loopCounter", loopCounter)
1308+
b.runtimeData.push("loopSignature", signature)
1309+
b.runtimeData.push("loopLabel", loopBegin.innerOutput(0))
1310+
},
1311+
GeneratorStub("WasmEndLoopWithSignatureGenerator", inContext: .single(.wasmFunction),
1312+
provides: [.wasmFunction]) { b in
1313+
let signature = b.runtimeData.pop("loopSignature")
1314+
let function = b.currentWasmModule.currentWasmFunction
12941315

1295-
function.wasmBuildLoop(with: parameters => outputTypes, args: args) {
1296-
label, loopArgs in
1297-
b.buildRecursive(n: defaultCodeGenerationAmount)
1316+
// Note that due to the do-while style implementation, the actual iteration count is at
1317+
// least 1.
1318+
let iterationCount = Int32.random(in: 0...16)
12981319
let loopCtr = function.wasmi32BinOp(
1299-
args[0], function.consti32(1), binOpKind: .Add)
1320+
b.runtimeData.pop("loopCounter"), function.consti32(1), binOpKind: .Add)
13001321
let condition = function.wasmi32CompareOp(
13011322
loopCtr, function.consti32(iterationCount), using: .Lt_s)
1302-
let backedgeArgs =
1303-
[loopCtr] + randomArgTypes.map { b.randomVariable(ofType: $0)! }
1323+
let wasmSignature = b.type(of: signature).wasmFunctionSignatureDefSignature
1324+
let backedgeArgs = [loopCtr] + wasmSignature.parameterTypes.dropFirst()
1325+
.map {b.randomVariable(ofType: $0)!}
13041326
function.wasmBranchIf(
1305-
condition, to: label, args: backedgeArgs,
1327+
condition, to: b.runtimeData.pop("loopLabel"), args: backedgeArgs,
13061328
hint: b.randomWasmBranchHint())
1307-
return outputTypes.map(function.findOrGenerateWasmVar)
1308-
}
1309-
},
1329+
let outputs = wasmSignature.outputTypes.map(function.findOrGenerateWasmVar)
1330+
b.emit(WasmEndLoop(outputCount: outputs.count), withInputs: [signature] + outputs)
1331+
},
1332+
]),
13101333

13111334
// TODO Turn this into a multi-part Generator
13121335
CodeGenerator("WasmLegacyTryCatchGenerator", inContext: .single(.wasmFunction)) {

Sources/Fuzzilli/FuzzIL/Instruction.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,12 +1395,11 @@ extension Instruction: ProtobufConvertible {
13951395
}
13961396
case .wasmBeginLoop(let op):
13971397
$0.wasmBeginLoop = Fuzzilli_Protobuf_WasmBeginLoop.with {
1398-
$0.parameterTypes = op.signature.parameterTypes.map(ILTypeToWasmTypeEnum)
1399-
$0.outputTypes = op.signature.outputTypes.map(ILTypeToWasmTypeEnum)
1398+
$0.parameterCount = Int32(op.parameterCount)
14001399
}
14011400
case .wasmEndLoop(let op):
14021401
$0.wasmEndLoop = Fuzzilli_Protobuf_WasmEndLoop.with {
1403-
$0.outputTypes = op.outputTypes.map(ILTypeToWasmTypeEnum)
1402+
$0.outputCount = Int32(op.numOutputs)
14041403
}
14051404
case .wasmBeginTryTable(let op):
14061405
$0.wasmBeginTryTable = Fuzzilli_Protobuf_WasmBeginTryTable.with {
@@ -2456,11 +2455,9 @@ extension Instruction: ProtobufConvertible {
24562455
case .wasmEndBlock(let p):
24572456
op = WasmEndBlock(outputTypes: p.outputTypes.map(WasmTypeEnumToILType))
24582457
case .wasmBeginLoop(let p):
2459-
let parameters = p.parameterTypes.map(WasmTypeEnumToILType)
2460-
let outputs = p.outputTypes.map(WasmTypeEnumToILType)
2461-
op = WasmBeginLoop(with: parameters => outputs)
2458+
op = WasmBeginLoop(parameterCount: Int(p.parameterCount))
24622459
case .wasmEndLoop(let p):
2463-
op = WasmEndLoop(outputTypes: p.outputTypes.map(WasmTypeEnumToILType))
2460+
op = WasmEndLoop(outputCount: Int(p.outputCount))
24642461
case .wasmBeginTryTable(let p):
24652462
let parameters = p.parameterTypes.map(WasmTypeEnumToILType)
24662463
let outputs = p.outputTypes.map(WasmTypeEnumToILType)

Sources/Fuzzilli/FuzzIL/JSTyper.swift

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public struct JSTyper: Analyzer {
3838
private var typeGroupDependencies: [Int:Set<Int>] = [:]
3939
private var selfReferences: [Variable: [(inout JSTyper, Variable?) -> ()]] = [:]
4040
private var isWithinTypeGroup = false
41+
// Tracks the type definition variable for each Wasm index type.
42+
private var wasmTypeDefMap = [WasmTypeDescription:Variable]()
4143

4244
// Tracks the active function definitions and contains the instruction that started the function.
4345
private var activeFunctionDefinitions = Stack<Instruction>()
@@ -401,6 +403,7 @@ public struct JSTyper: Analyzer {
401403
state.reset()
402404
signatures.removeAll()
403405
typeGroups.removeAll()
406+
wasmTypeDefMap.removeAll()
404407
defUseAnalyzer = DefUseAnalyzer()
405408
isWithinTypeGroup = false
406409
dynamicObjectGroupManager = ObjectGroupManager()
@@ -432,6 +435,20 @@ public struct JSTyper: Analyzer {
432435
}
433436
}
434437

438+
mutating func registerWasmTypeDef(_ def: Variable) {
439+
let desc = type(of: def).wasmTypeDefinition!.description!
440+
assert(wasmTypeDefMap[desc] == nil, "duplicate type description")
441+
wasmTypeDefMap[desc] = def
442+
}
443+
444+
func getWasmTypeDef(for type: ILType) -> Variable {
445+
assert(type.isWasmReferenceType)
446+
guard case .Index(let desc) = type.wasmReferenceType!.kind else {
447+
fatalError("\(type) is not an index type")
448+
}
449+
return wasmTypeDefMap[desc.get()!]!
450+
}
451+
435452
mutating func addSignatureType(def: Variable, signature: WasmSignature, inputs: ArraySlice<Variable>) {
436453
assert(isWithinTypeGroup)
437454
var inputs = inputs.makeIterator()
@@ -445,7 +462,7 @@ public struct JSTyper: Analyzer {
445462
if paramType.requiredInputCount() == 0 {
446463
return paramType
447464
}
448-
assert(paramType.Is(.wasmRef(.Index(), nullability: true)))
465+
assert(paramType.Is(.anyIndexRef))
449466
let typeDef = inputs.next()!
450467
let elementDesc = type(of: typeDef).wasmTypeDefinition!.description!
451468
if elementDesc == .selfReference {
@@ -809,16 +826,18 @@ public struct JSTyper: Analyzer {
809826
wasmTypeBeginBlock(instr, op.signature)
810827
case .wasmEndIf(let op):
811828
wasmTypeEndBlock(instr, op.outputTypes)
812-
case .wasmBeginLoop(let op):
829+
case .wasmBeginLoop(_):
813830
// Note that different to all other blocks the loop's label parameters are the input types
814831
// of the block, not the result types (because a branch to a loop label jumps to the
815832
// beginning of the loop block instead of the end.)
816-
setType(of: instr.innerOutputs.first!, to: .label(op.signature.parameterTypes))
817-
for (innerOutput, paramType) in zip(instr.innerOutputs.dropFirst(), op.signature.parameterTypes) {
833+
let signature = type(of: instr.input(0)).wasmFunctionSignatureDefSignature
834+
setType(of: instr.innerOutputs.first!, to: .label(signature.parameterTypes))
835+
for (innerOutput, paramType) in zip(instr.innerOutputs.dropFirst(), signature.parameterTypes) {
818836
setType(of: innerOutput, to: paramType)
819837
}
820-
case .wasmEndLoop(let op):
821-
wasmTypeEndBlock(instr, op.outputTypes)
838+
case .wasmEndLoop(_):
839+
let signature = type(of: instr.input(0)).wasmFunctionSignatureDefSignature
840+
wasmTypeEndBlock(instr, signature.outputTypes)
822841
case .wasmBeginTryTable(let op):
823842
wasmTypeBeginBlock(instr, op.signature)
824843
instr.inputs.forEach { input in
@@ -903,6 +922,7 @@ public struct JSTyper: Analyzer {
903922
startTypeGroup()
904923
addSignatureType(def: instr.output, signature: op.signature, inputs: instr.inputs)
905924
finishTypeGroup()
925+
registerWasmTypeDef(instr.output)
906926
default:
907927
if instr.numInnerOutputs + instr.numOutputs != 0 {
908928
fatalError("Missing typing of outputs for \(instr.op.opcode)")
@@ -1886,6 +1906,9 @@ public struct JSTyper: Analyzer {
18861906
set(output, state.type(of: input))
18871907
}
18881908
finishTypeGroup()
1909+
for output in instr.outputs {
1910+
registerWasmTypeDef(output)
1911+
}
18891912

18901913
case .wasmDefineSignatureType(let op):
18911914
addSignatureType(def: instr.output, signature: op.signature, inputs: instr.inputs)

Sources/Fuzzilli/FuzzIL/TypeSystem.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ public struct ILType: Hashable {
291291
public static let wasmNumericalPrimitive = .wasmi32 | .wasmi64 | .wasmf32 | .wasmf64
292292

293293
public static let anyNonNullableIndexRef = wasmRef(.Index(), nullability: false)
294+
public static let anyIndexRef = wasmRef(.Index(), nullability: true)
294295

295296
//
296297
// Type testing
@@ -596,6 +597,14 @@ public struct ILType: Hashable {
596597
return (wasmType as! WasmFunctionDefinition).signature
597598
}
598599

600+
public var wasmFunctionSignatureDefSignature: WasmSignature {
601+
let desc = (wasmType as? WasmTypeDefinition)?.description
602+
guard let desc = desc as? WasmSignatureTypeDescription else {
603+
fatalError("\(self) is not a Wasm signature type defintion")
604+
}
605+
return desc.signature
606+
}
607+
599608
public var isWasmDefaultable: Bool {
600609
let isPacked = Is(.wasmPackedI8) || Is(.wasmPackedI16)
601610
return isPacked

Sources/Fuzzilli/FuzzIL/WasmOperations.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,21 +1315,26 @@ final class WasmEndIf: WasmOperation {
13151315

13161316
final class WasmBeginLoop: WasmOperation {
13171317
override var opcode: Opcode { .wasmBeginLoop(self) }
1318-
let signature: WasmSignature
13191318

1320-
init(with signature: WasmSignature) {
1321-
self.signature = signature
1322-
super.init(numInputs: signature.parameterTypes.count, numInnerOutputs: 1 + signature.parameterTypes.count, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction])
1319+
init(parameterCount: Int) {
1320+
// Inputs: the signature + the inputs to the loop.
1321+
// inner outputs: The loop label + the arguments of the loop.
1322+
super.init(numInputs: 1 + parameterCount, numInnerOutputs: 1 + parameterCount, attributes: [.isBlockStart, .propagatesSurroundingContext], requiredContext: [.wasmFunction])
1323+
}
1324+
1325+
convenience init(with signature: WasmSignature) {
1326+
self.init(parameterCount: signature.parameterTypes.count)
13231327
}
1328+
1329+
var parameterCount: Int {numInputs - 1}
13241330
}
13251331

13261332
final class WasmEndLoop: WasmOperation {
13271333
override var opcode: Opcode { .wasmEndLoop(self) }
1328-
let outputTypes: [ILType]
13291334

1330-
init(outputTypes: [ILType] = []) {
1331-
self.outputTypes = outputTypes
1332-
super.init(numInputs: outputTypes.count, numOutputs: outputTypes.count, attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction])
1335+
init(outputCount: Int) {
1336+
// Inputs: the signature + the outputs of the loop.
1337+
super.init(numInputs: 1 + outputCount, numOutputs: outputCount, attributes: [.isBlockEnd, .resumesSurroundingContext], requiredContext: [.wasmFunction])
13331338
}
13341339
}
13351340

Sources/Fuzzilli/Lifting/FuzzILLifter.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,9 +1097,9 @@ public class FuzzILLifter: Lifter {
10971097
w.emit("WasmEndBlock \(inputs)")
10981098
}
10991099

1100-
case .wasmBeginLoop(let op):
1100+
case .wasmBeginLoop(_):
11011101
let inputs = instr.inputs.map(lift).joined(separator: ", ")
1102-
w.emit("WasmBeginLoop (\(op.signature)) [\(inputs)] -> L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))]")
1102+
w.emit("WasmBeginLoop [\(inputs)] -> L:\(instr.innerOutput(0)) [\(liftCallArguments(instr.innerOutputs(1...)))]")
11031103
w.increaseIndentionLevel()
11041104

11051105
case .wasmEndLoop(let op):

0 commit comments

Comments
 (0)