Skip to content

Commit 95e6420

Browse files
Dominik KlembaV8-internal LUCI CQ
authored andcommitted
[wasm] Implement initial WebAssembly atomics support
- add atomic load operations - add atomic store operations Bug: 427134598 Change-Id: I8c98733a0958c3b0e95ca3f2bce7b7e0e02d91cb Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/8443158 Reviewed-by: Matthias Liedtke <[email protected]> Commit-Queue: Dominik Klemba <[email protected]>
1 parent 2813534 commit 95e6420

16 files changed

+1004
-255
lines changed

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3302,6 +3302,17 @@ public class ProgramBuilder {
33023302
b.emit(WasmMemoryStore(storeType: storeType, staticOffset: staticOffset), withInputs: [memory, dynamicOffset, value], types: inputTypes)
33033303
}
33043304

3305+
@discardableResult
3306+
func wasmAtomicLoad(memory: Variable, address: Variable, loadType: WasmAtomicLoadType, offset: Int64) -> Variable {
3307+
let op = WasmAtomicLoad(loadType: loadType, offset: offset)
3308+
return b.emit(op, withInputs: [memory, address]).output
3309+
}
3310+
3311+
func wasmAtomicStore(memory: Variable, address: Variable, value: Variable, storeType: WasmAtomicStoreType, offset: Int64) {
3312+
let op = WasmAtomicStore(storeType: storeType, offset: offset)
3313+
b.emit(op, withInputs: [memory, address, value])
3314+
}
3315+
33053316
@discardableResult
33063317
public func wasmMemorySize(memory: Variable) -> Variable {
33073318
return b.emit(WasmMemorySize(), withInputs: [memory],
@@ -3869,17 +3880,37 @@ public class ProgramBuilder {
38693880
return memoryTypeInfo.limits.min == 0
38703881
}
38713882

3883+
// Returns 'dynamicOffset' and 'staticOffset' such that:
3884+
// 0 <= dynamicOffset + staticOffset <= memSize
3885+
//
3886+
// Note: In rare cases, the returned values may lead to an out-of-bounds memory access.
38723887
func generateMemoryIndexes(forMemory memory: Variable) -> (Variable, Int64) {
3888+
return generateAlignedMemoryIndexes(forMemory: memory, alignment: 1)
3889+
}
3890+
3891+
// Returns 'dynamicOffset' and 'alignedStaticOffset' such that:
3892+
// 0 <= dynamicOffset + alignedStaticOffset <= memSize
3893+
// (dynamicOffset + alignedStaticOffset) % alignment == 0
3894+
//
3895+
// Note: In rare cases, the returned values may lead to an out-of-bounds memory access.
3896+
func generateAlignedMemoryIndexes(forMemory memory: Variable, alignment: Int64) -> (address: Variable, offset: Int64) {
38733897
let memoryTypeInfo = self.type(of: memory).wasmMemoryType!
38743898
let memSize = Int64(memoryTypeInfo.limits.min * WasmConstants.specWasmMemPageSize)
38753899
let function = self.currentWasmModule.currentWasmFunction
3900+
assert(memSize >= alignment, "Memory size must be large enough to satisfy alignment")
3901+
assert(alignment > 0, "Alignment must be positive")
38763902

3877-
// Generate an in-bounds offset (dynamicOffset + staticOffset) into the memory.
3878-
let dynamicOffsetValue = self.randomNonNegativeIndex(upTo: memSize)
3903+
// Generate an in-bounds offset (dynamicOffset + alignedStaticOffset) into the memory.
3904+
// The '+1' allows out-of-bounds access (dynamicOffset + alignedStaticOffset == memSize)
3905+
let dynamicOffsetValue = self.randomNonNegativeIndex(upTo: memSize - alignment + 1)
38793906
let dynamicOffset = function.memoryArgument(dynamicOffsetValue, memoryTypeInfo)
3880-
let staticOffset = self.randomNonNegativeIndex(upTo: memSize - dynamicOffsetValue)
3907+
let staticOffset = self.randomNonNegativeIndex(upTo: memSize - alignment + 1 - dynamicOffsetValue)
38813908

3882-
return (dynamicOffset, staticOffset)
3909+
let currentAddress = dynamicOffsetValue + staticOffset
3910+
// Calculate the minimal value needed to make the total address aligned.
3911+
let adjustment = (alignment - (currentAddress % alignment)) % alignment
3912+
let alignedStaticOffset = staticOffset + adjustment
3913+
return (dynamicOffset, alignedStaticOffset)
38833914
}
38843915

38853916
public func randomWasmGlobal() -> WasmGlobal {

Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ public let codeGeneratorWeights = [
218218
"WasmDefineMemoryGenerator": 8,
219219
"WasmMemoryLoadGenerator": 10,
220220
"WasmMemoryStoreGenerator": 10,
221+
"WasmAtomicLoadGenerator": 10,
222+
"WasmAtomicStoreGenerator": 10,
221223
"WasmMemorySizeGenerator": 5,
222224
"WasmMemoryGrowGenerator": 1,
223225
"WasmMemoryFillGenerator": 5,

Sources/Fuzzilli/CodeGen/WasmCodeGenerators.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,28 @@ public let WasmCodeGenerators: [CodeGenerator] = [
339339
}
340340
},
341341

342+
CodeGenerator("WasmAtomicLoadGenerator", inContext: .wasmFunction, inputs: .required(.object(ofGroup: "WasmMemory"))) { b, memory in
343+
let function = b.currentWasmModule.currentWasmFunction
344+
let loadType = chooseUniform(from: WasmAtomicLoadType.allCases)
345+
let alignment = loadType.naturalAlignment()
346+
347+
let (address, staticOffset) = b.generateAlignedMemoryIndexes(forMemory: memory, alignment: alignment)
348+
349+
function.wasmAtomicLoad(memory: memory, address: address, loadType: loadType, offset: staticOffset)
350+
},
351+
352+
CodeGenerator("WasmAtomicStoreGenerator", inContext: .wasmFunction, inputs: .required(.object(ofGroup: "WasmMemory"))) { b, memory in
353+
let function = b.currentWasmModule.currentWasmFunction
354+
let storeType = chooseUniform(from: WasmAtomicStoreType.allCases)
355+
let alignment = storeType.naturalAlignment()
356+
357+
guard let value = b.randomVariable(ofType: storeType.numberType()) else { return }
358+
359+
let (address, staticOffset) = b.generateAlignedMemoryIndexes(forMemory: memory, alignment: alignment)
360+
361+
function.wasmAtomicStore(memory: memory, address: address, value: value, storeType: storeType, offset: staticOffset)
362+
},
363+
342364
CodeGenerator("WasmMemorySizeGenerator", inContext: .wasmFunction, inputs: .required(.object(ofGroup: "WasmMemory"))) { b, memory in
343365
let function = b.currentWasmModule.currentWasmFunction
344366
function.wasmMemorySize(memory: memory)

Sources/Fuzzilli/FuzzIL/Instruction.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,16 @@ extension Instruction: ProtobufConvertible {
12951295
$0.storeType = convertWasmMemoryStoreType(op.storeType);
12961296
$0.staticOffset = op.staticOffset
12971297
}
1298+
case .wasmAtomicLoad(let op):
1299+
$0.wasmAtomicLoad = Fuzzilli_Protobuf_WasmAtomicLoad.with {
1300+
$0.loadType = convertEnum(op.loadType, WasmAtomicLoadType.allCases)
1301+
$0.offset = op.offset
1302+
}
1303+
case .wasmAtomicStore(let op):
1304+
$0.wasmAtomicStore = Fuzzilli_Protobuf_WasmAtomicStore.with {
1305+
$0.storeType = convertEnum(op.storeType, WasmAtomicStoreType.allCases)
1306+
$0.offset = op.offset
1307+
}
12981308
case .wasmMemorySize(_):
12991309
$0.wasmMemorySize = Fuzzilli_Protobuf_WasmMemorySize()
13001310
case .wasmMemoryGrow(_):
@@ -2481,6 +2491,10 @@ extension Instruction: ProtobufConvertible {
24812491
op = WasmRefI31()
24822492
case .wasmI31Get(let p):
24832493
op = WasmI31Get(isSigned: p.isSigned)
2494+
case .wasmAtomicLoad(let p):
2495+
op = WasmAtomicLoad(loadType: try convertEnum(p.loadType, WasmAtomicLoadType.allCases), offset: p.offset)
2496+
case .wasmAtomicStore(let p):
2497+
op = WasmAtomicStore(storeType: try convertEnum(p.storeType, WasmAtomicStoreType.allCases), offset: p.offset)
24842498
}
24852499

24862500
guard op.numInputs + op.numOutputs + op.numInnerOutputs == inouts.count else {

Sources/Fuzzilli/FuzzIL/JSTyper.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,13 @@ public struct JSTyper: Analyzer {
687687
let definingInstruction = defUseAnalyzer.definition(of: instr.input(0))
688688
dynamicObjectGroupManager.addWasmMemory(withType: type(of: instr.input(0)), forDefinition: definingInstruction, forVariable: instr.input(0))
689689
setType(of: instr.output, to: op.loadType.numberType())
690+
case .wasmAtomicLoad(let op):
691+
let definingInstruction = defUseAnalyzer.definition(of: instr.input(0))
692+
dynamicObjectGroupManager.addWasmMemory(withType: type(of: instr.input(0)), forDefinition: definingInstruction, forVariable: instr.input(0))
693+
setType(of: instr.output, to: op.loadType.numberType())
694+
case .wasmAtomicStore(_):
695+
let definingInstruction = defUseAnalyzer.definition(of: instr.input(0))
696+
dynamicObjectGroupManager.addWasmMemory(withType: type(of: instr.input(0)), forDefinition: definingInstruction, forVariable: instr.input(0))
690697
case .wasmMemorySize(_),
691698
.wasmMemoryGrow(_):
692699
let isMemory64 = type(of: instr.input(0)).wasmMemoryType?.isMemory64 ?? false

Sources/Fuzzilli/FuzzIL/Opcodes.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ enum Opcode {
283283
case wasmReturnCallIndirect(WasmReturnCallIndirect)
284284
case wasmMemoryLoad(WasmMemoryLoad)
285285
case wasmMemoryStore(WasmMemoryStore)
286+
case wasmAtomicLoad(WasmAtomicLoad)
287+
case wasmAtomicStore(WasmAtomicStore)
286288
case wasmMemorySize(WasmMemorySize)
287289
case wasmMemoryGrow(WasmMemoryGrow)
288290
case wasmMemoryFill(WasmMemoryFill)

Sources/Fuzzilli/FuzzIL/WasmOperations.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,70 @@ final class WasmMemorySize: WasmOperation {
949949
}
950950
}
951951

952+
public enum WasmAtomicLoadType: UInt8, CaseIterable {
953+
case i32Load = 0x10
954+
case i64Load = 0x11
955+
case i32Load8U = 0x12
956+
case i32Load16U = 0x13
957+
case i64Load8U = 0x14
958+
case i64Load16U = 0x15
959+
case i64Load32U = 0x16
960+
961+
func numberType() -> ILType {
962+
switch self {
963+
case .i32Load, .i32Load8U, .i32Load16U:
964+
return .wasmi32
965+
case .i64Load, .i64Load8U, .i64Load16U, .i64Load32U:
966+
return .wasmi64
967+
}
968+
}
969+
970+
func naturalAlignment() -> Int64 {
971+
switch self {
972+
case .i32Load8U, .i64Load8U:
973+
return 1
974+
case .i32Load16U, .i64Load16U:
975+
return 2
976+
case .i32Load, .i64Load32U:
977+
return 4
978+
case .i64Load:
979+
return 8
980+
}
981+
}
982+
}
983+
984+
public enum WasmAtomicStoreType: UInt8, CaseIterable {
985+
case i32Store = 0x17
986+
case i64Store = 0x18
987+
case i32Store8 = 0x19
988+
case i32Store16 = 0x1a
989+
case i64Store8 = 0x1b
990+
case i64Store16 = 0x1c
991+
case i64Store32 = 0x1d
992+
993+
func numberType() -> ILType {
994+
switch self {
995+
case .i32Store, .i32Store8, .i32Store16:
996+
return .wasmi32
997+
case .i64Store, .i64Store8, .i64Store16, .i64Store32:
998+
return .wasmi64
999+
}
1000+
}
1001+
1002+
func naturalAlignment() -> Int64 {
1003+
switch self {
1004+
case .i32Store8, .i64Store8:
1005+
return 1
1006+
case .i32Store16, .i64Store16:
1007+
return 2
1008+
case .i32Store, .i64Store32:
1009+
return 4
1010+
case .i64Store:
1011+
return 8
1012+
}
1013+
}
1014+
}
1015+
9521016
// Grows a memory by the provided amount of pages (each page being 64KB). Returns the old size of
9531017
// the memory before growing. Returns -1 if growing would exceed the maximum size of that memory or
9541018
// if resource allocation fails.
@@ -2030,3 +2094,37 @@ class WasmI31Get: WasmOperation {
20302094
super.init(numInputs: 1, numOutputs: 1, requiredContext: [.wasmFunction])
20312095
}
20322096
}
2097+
2098+
/// An atomic load from Wasm memory.
2099+
/// The accessed address is base + offset.
2100+
final class WasmAtomicLoad: WasmOperation {
2101+
override var opcode: Opcode { .wasmAtomicLoad(self) }
2102+
2103+
/// The type of the loaded value.
2104+
let loadType: WasmAtomicLoadType
2105+
/// The static offset from the base address.
2106+
let offset: Int64
2107+
2108+
init(loadType: WasmAtomicLoadType, offset: Int64) {
2109+
self.loadType = loadType
2110+
self.offset = offset
2111+
super.init(numInputs: 2, numOutputs: 1, attributes: [.isMutable], requiredContext: [.wasmFunction])
2112+
}
2113+
}
2114+
2115+
/// An atomic store to Wasm memory.
2116+
/// The accessed address is base + offset.
2117+
final class WasmAtomicStore: WasmOperation {
2118+
override var opcode: Opcode { .wasmAtomicStore(self) }
2119+
2120+
/// The type of the stored value.
2121+
let storeType: WasmAtomicStoreType
2122+
/// The static offset from the base address.
2123+
let offset: Int64
2124+
2125+
init(storeType: WasmAtomicStoreType, offset: Int64) {
2126+
self.storeType = storeType
2127+
self.offset = offset
2128+
super.init(numInputs: 3, numOutputs: 0, attributes: [.isMutable], requiredContext: [.wasmFunction])
2129+
}
2130+
}

Sources/Fuzzilli/Lifting/FuzzILLifter.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,12 @@ public class FuzzILLifter: Lifter {
847847
case .wasmMemoryStore(let op):
848848
w.emit("WasmMemoryStore '\(op.storeType)' \(input(0))[\(input(1)) + \(op.staticOffset)] <- \(input(2))")
849849

850+
case .wasmAtomicLoad(let op):
851+
w.emit("\(output()) <- WasmAtomicLoad \(input(0))[\(input(1)) + \(op.offset)] [\(op.loadType)]")
852+
853+
case .wasmAtomicStore(let op):
854+
w.emit("WasmAtomicStore \(input(0))[\(input(1)) + \(op.offset)] <- \(input(2)) [\(op.storeType)]")
855+
850856
case .wasmMemorySize(_):
851857
w.emit("\(output()) <- WasmMemorySize \(input(0))")
852858

Sources/Fuzzilli/Lifting/JavaScriptLifter.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,6 +1622,8 @@ public class JavaScriptLifter: Lifter {
16221622
.wasmReturnCallIndirect(_),
16231623
.wasmMemoryLoad(_),
16241624
.wasmMemoryStore(_),
1625+
.wasmAtomicLoad(_),
1626+
.wasmAtomicStore(_),
16251627
.wasmMemorySize(_),
16261628
.wasmMemoryGrow(_),
16271629
.wasmMemoryFill(_),

Sources/Fuzzilli/Lifting/WasmLifter.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,11 +1517,25 @@ public class WasmLifter {
15171517

15181518
throw WasmLifter.CompileError.failedIndexLookUp
15191519
}
1520-
1521-
private func alignmentAndMemoryBytes(_ memory: Variable) throws -> Data {
1522-
// The memory immediate is either {align = 0, staticOffset} for memory 0, or {align = 0x40, memoryIdx, staticOffset}. Use signed encoding for potential bad (i.e. negative) offsets.
1520+
// The memory immediate argument, which encodes the alignment and memory index.
1521+
// For memory 0, this is just the alignment. For other memories, a flag is set
1522+
// and the memory index is also encoded.
1523+
// Memory zero: `[align]`.
1524+
// Multi-memory: `[align | 0x40] + [mem_idx]`.
1525+
private func alignmentAndMemoryBytes(_ memory: Variable, alignment: Int64 = 1) throws -> Data {
1526+
assert(alignment > 0 && (alignment & (alignment - 1)) == 0, "Alignment must be a power of two")
15231527
let memoryIdx = try resolveIdx(ofType: .memory, for: memory)
1524-
return memoryIdx == 0 ? Data([0]) : (Data([0x40]) + Leb128.unsignedEncode(memoryIdx))
1528+
1529+
1530+
let alignmentLog2 = alignment.trailingZeroBitCount
1531+
assert(alignmentLog2 < 0x40, "Alignment \(alignment) is too large for multi-memory encoding")
1532+
1533+
if memoryIdx == 0 {
1534+
return Leb128.unsignedEncode(alignmentLog2)
1535+
} else {
1536+
let flags = UInt8(alignmentLog2) | 0x40
1537+
return Data([flags]) + Leb128.unsignedEncode(memoryIdx)
1538+
}
15251539
}
15261540

15271541
private func branchDepthFor(label: Variable) throws -> Int {
@@ -1731,6 +1745,15 @@ public class WasmLifter {
17311745
? [op.storeType.rawValue]
17321746
: [Prefix.Simd.rawValue, op.storeType.rawValue])
17331747
return opCode + alignAndMemory + Leb128.signedEncode(Int(op.staticOffset))
1748+
case .wasmAtomicLoad(let op):
1749+
let opcode = [Prefix.Atomic.rawValue, op.loadType.rawValue]
1750+
let alignAndMemory = try alignmentAndMemoryBytes(wasmInstruction.input(0), alignment: op.loadType.naturalAlignment())
1751+
return Data(opcode) + alignAndMemory + Leb128.signedEncode(Int(op.offset))
1752+
1753+
case .wasmAtomicStore(let op):
1754+
let opcode = [Prefix.Atomic.rawValue, op.storeType.rawValue]
1755+
let alignAndMemory = try alignmentAndMemoryBytes(wasmInstruction.input(0), alignment: op.storeType.naturalAlignment())
1756+
return Data(opcode) + alignAndMemory + Leb128.signedEncode(Int(op.offset))
17341757
case .wasmMemorySize(_):
17351758
let memoryIdx = try resolveIdx(ofType: .memory, for: wasmInstruction.input(0))
17361759
return Data([0x3F]) + Leb128.unsignedEncode(memoryIdx)

0 commit comments

Comments
 (0)