@@ -19,6 +19,38 @@ import Foundation
1919/// This provides methods for constructing and appending random
2020/// instances of the different kinds of operations in a program.
2121public class ProgramBuilder {
22+
23+ /// Runtime data used by code generators to share data between different stubs inside the same
24+ /// code generator. It is strictly required that all pushed values are also popped again in the
25+ /// same code generator.
26+ public class GeneratorRuntimeData {
27+ private var data = [ String : Stack < Variable > ] ( )
28+
29+ public func push( _ key: String , _ value: Variable ) {
30+ data [ key, default: . init( ) ] . push ( value)
31+ }
32+
33+ public func pop( _ key: String ) -> Variable {
34+ assert ( data [ key] != nil )
35+ return data [ key] !. pop ( )
36+ }
37+
38+ // Fetch the most recent value for this key and push it back.
39+ public func popAndPush( _ key: String ) -> Variable {
40+ assert ( data [ key] != nil )
41+ return data [ key] !. top
42+ }
43+
44+ func reset( ) {
45+ #if DEBUG
46+ for (key, value) in data {
47+ assert ( value. isEmpty, " Stale entries for runtime data ' \( key) ' " )
48+ }
49+ #endif
50+ data. removeAll ( keepingCapacity: false )
51+ }
52+ }
53+
2254 /// The fuzzer instance for which this builder is active.
2355 public let fuzzer : Fuzzer
2456
@@ -147,6 +179,9 @@ public class ProgramBuilder {
147179 /// The remaining CodeGenerators to call as part of a building / CodeGen step, these will "clean up" the state and fix the contexts.
148180 public var scheduled : Stack < GeneratorStub > = Stack ( )
149181
182+ // Runtime data that can be shared between different stubs within a CodeGenerator.
183+ var runtimeData = GeneratorRuntimeData ( )
184+
150185 /// Stack of active switch blocks.
151186 private var activeSwitchBlocks = Stack < SwitchBlock > ( )
152187
@@ -214,6 +249,7 @@ public class ProgramBuilder {
214249 activeObjectLiterals. removeAll ( )
215250 activeClassDefinitions. removeAll ( )
216251 buildLog? . reset ( )
252+ runtimeData. reset ( )
217253 }
218254
219255 /// Finalizes and returns the constructed program, then resets this builder so it can be reused for building another program.
@@ -483,6 +519,36 @@ public class ProgramBuilder {
483519 return probability ( 0.5 ) ? randomBuiltinMethodName ( ) : randomCustomMethodName ( )
484520 }
485521
522+ private static func generateConstrained< T: Equatable > (
523+ _ generator: ( ) -> T ,
524+ notIn: any Collection < T > ,
525+ fallback: ( ) -> T ) -> T {
526+ var result : T
527+ var attempts = 0
528+ repeat {
529+ if attempts >= 10 {
530+ return fallback ( )
531+ }
532+ result = generator ( )
533+ attempts += 1
534+ } while notIn. contains ( result)
535+ return result
536+ }
537+
538+ // Generate a string not already contained in `notIn` using the provided `generator`. If it fails
539+ // repeatedly, return a random string instead.
540+ public func generateString( _ generator: ( ) -> String , notIn: any Collection < String > ) -> String {
541+ Self . generateConstrained ( generator, notIn: notIn,
542+ fallback: { String . random ( ofLength: Int . random ( in: 1 ... 5 ) ) } )
543+ }
544+
545+ // Find a random variable to use as a string that isn't contained in `notIn`. If it fails
546+ // repeatedly, create a random string literal instead.
547+ public func findOrGenerateStringLikeVariable( notIn: any Collection < Variable > ) -> Variable {
548+ return Self . generateConstrained ( randomJsVariable, notIn: notIn,
549+ fallback: { loadString ( String . random ( ofLength: Int . random ( in: 1 ... 5 ) ) ) } )
550+ }
551+
486552 // Settings and constants controlling the behavior of randomParameters() below.
487553 // This determines how many variables of a given type need to be visible before
488554 // that type is considered a candidate for a parameter type. For example, if this
@@ -500,15 +566,15 @@ public class ProgramBuilder {
500566 //
501567 // This will attempt to find a parameter types for which at least a few variables of a compatible types are
502568 // currently available to (potentially) later be used as arguments for calling the generated subroutine.
503- public func randomParameters( n wantedNumberOfParameters: Int ? = nil ) -> SubroutineDescriptor {
569+ public func randomParameters( n wantedNumberOfParameters: Int ? = nil , withRestParameterProbability restProbability : Double = 0.2 ) -> SubroutineDescriptor {
504570 assert ( probabilityOfUsingAnythingAsParameterTypeIfAvoidable >= 0 && probabilityOfUsingAnythingAsParameterTypeIfAvoidable <= 1 )
505571
506572 // If the caller didn't specify how many parameters to generated, find an appropriate
507573 // number of parameters based on how many variables are currently visible (and can
508574 // therefore later be used as arguments for calling the new function).
509575 let n : Int
510576 if let requestedN = wantedNumberOfParameters {
511- assert ( requestedN > 0 )
577+ assert ( requestedN >= 0 )
512578 n = requestedN
513579 } else {
514580 switch numberOfVisibleVariables {
@@ -537,15 +603,27 @@ public class ProgramBuilder {
537603 }
538604
539605 var params = ParameterList ( )
540- for _ in 0 ..< n {
606+
607+ let generateRestParameter = n > 0 && probability ( restProbability)
608+ let numPlainParams = generateRestParameter ? n - 1 : n
609+
610+ func randomParamType( ) -> ILType {
541611 if probability ( probabilityOfUsingAnythingAsParameterTypeIfAvoidable) {
542- params . append ( . jsAnything)
612+ return . jsAnything
543613 } else {
544- params . append ( . plain ( chooseUniform ( from: candidates) ) )
614+ return chooseUniform ( from: candidates)
545615 }
546616 }
547617
548- // TODO: also generate rest parameters and maybe even optional ones sometimes?
618+ for _ in 0 ..< numPlainParams {
619+ params. append ( . plain( randomParamType ( ) ) )
620+ }
621+
622+ if generateRestParameter {
623+ params. append ( . rest( randomParamType ( ) ) )
624+ }
625+
626+ // TODO: also generate optional parameters sometimes?
549627
550628 return . parameters( params)
551629 }
@@ -686,6 +764,13 @@ public class ProgramBuilder {
686764 // Note that builtin constructors are handled above in the maybeGenerateConstructorAsPath call.
687765 return self . randomVariable ( forUseAs: . function( ) )
688766 } ) ,
767+ ( . unboundFunction( ) , {
768+ // TODO: We have the same issue as above for functions.
769+ // First try to find an existing unbound function. if not present, try to find any
770+ // function. Using any function as an unbound function is fine, it just misses the
771+ // information about the receiver type (which for many functions doesn't matter).
772+ return self . randomVariable ( ofType: . unboundFunction( ) ) ?? self . randomVariable ( forUseAs: . function( ) )
773+ } ) ,
689774 ( . undefined, { return self . loadUndefined ( ) } ) ,
690775 ( . constructor( ) , {
691776 // TODO: We have the same issue as above for functions.
@@ -2038,7 +2123,7 @@ public class ProgramBuilder {
20382123
20392124 var numberOfGeneratedInstructions = 0
20402125
2041- // calculate all input requirements of this CodeGenerator.
2126+ // Calculate all input requirements of this CodeGenerator.
20422127 let inputTypes = Set ( generator. parts. reduce ( [ ] ) { res, gen in
20432128 return res + gen. inputs. types
20442129 } )
@@ -3823,17 +3908,20 @@ public class ProgramBuilder {
38233908 }
38243909
38253910 public func wasmTableInit( elementSegment: Variable , table: Variable , tableOffset: Variable , elementSegmentOffset: Variable , nrOfElementsToUpdate: Variable ) {
3826- // TODO: b/427115604 - assert that table.elemType IS_SUBTYPE_OF elementSegment.elemType (depending on refactor outcome).
3911+ let elementSegmentType = ILType . wasmFuncRef
3912+ let tableElemType = b. type ( of: table) . wasmTableType!. elementType
3913+ assert ( elementSegmentType. Is ( tableElemType) )
3914+
38273915 let addrType = b. type ( of: table) . wasmTableType!. isTable64 ? ILType . wasmi64 : ILType . wasmi32
38283916 b. emit ( WasmTableInit ( ) , withInputs: [ elementSegment, table, tableOffset, elementSegmentOffset, nrOfElementsToUpdate] ,
3829- types: [ . wasmElementSegment( ) , . object( ofGroup: " WasmTable " ) , addrType, addrType , addrType ] )
3917+ types: [ . wasmElementSegment( ) , . object( ofGroup: " WasmTable " ) , addrType, . wasmi32 , . wasmi32 ] )
38303918 }
38313919
38323920 public func wasmTableCopy( dstTable: Variable , srcTable: Variable , dstOffset: Variable , srcOffset: Variable , count: Variable ) {
3833- // TODO: b/427115604 - assert that srcTable.elemType IS_SUBTYPE_OF dstTable.elemType (depending on refactor outcome).
38343921 let dstTableType = b. type ( of: dstTable) . wasmTableType!
38353922 let srcTableType = b. type ( of: srcTable) . wasmTableType!
38363923 assert ( dstTableType. isTable64 == srcTableType. isTable64)
3924+ assert ( srcTableType. elementType. Is ( dstTableType. elementType) )
38373925
38383926 let addrType = dstTableType. isTable64 ? ILType . wasmi64 : ILType . wasmi32
38393927 b. emit ( WasmTableCopy ( ) , withInputs: [ dstTable, srcTable, dstOffset, srcOffset, count] ,
@@ -4354,21 +4442,27 @@ public class ProgramBuilder {
43544442
43554443 @discardableResult
43564444 public func addTable( elementType: ILType , minSize: Int , maxSize: Int ? = nil , definedEntries: [ WasmTableType . IndexInTableAndWasmSignature ] = [ ] , definedEntryValues: [ Variable ] = [ ] , isTable64: Bool ) -> Variable {
4357- let inputTypes = if elementType == . wasmFuncRef {
4358- Array ( repeating: . wasmFunctionDef( ) | . function( ) , count: definedEntries. count)
4359- } else {
4360- [ ILType] ( )
4361- }
4445+ let inputTypes = Array ( repeating: getEntryTypeForTable ( elementType: elementType) , count: definedEntries. count)
43624446 return b. emit ( WasmDefineTable ( elementType: elementType, limits: Limits ( min: minSize, max: maxSize) , definedEntries: definedEntries, isTable64: isTable64) ,
43634447 withInputs: definedEntryValues, types: inputTypes) . output
43644448 }
43654449
43664450 @discardableResult
4367- public func addElementSegment( elementsType : ILType , elements: [ Variable ] ) -> Variable {
4368- let inputTypes = Array ( repeating: elementsType , count: elements. count)
4451+ public func addElementSegment( elements: [ Variable ] ) -> Variable {
4452+ let inputTypes = Array ( repeating: getEntryTypeForTable ( elementType : ILType . wasmFuncRef ) , count: elements. count)
43694453 return b. emit ( WasmDefineElementSegment ( size: UInt32 ( elements. count) ) , withInputs: elements, types: inputTypes) . output
43704454 }
43714455
4456+ public func getEntryTypeForTable( elementType: ILType ) -> ILType {
4457+ switch elementType {
4458+ case . wasmFuncRef:
4459+ return . wasmFunctionDef( ) | . function( )
4460+ default :
4461+ return . object( )
4462+ }
4463+ }
4464+
4465+
43724466 // This result can be ignored right now, as we can only define one memory per module
43734467 // Also this should be tracked like a global / table.
43744468 @discardableResult
0 commit comments