Skip to content

Commit 2813534

Browse files
LiedtkeV8-internal LUCI CQ
authored andcommitted
[wasm] Implement subtyping between all abstract heap types
This implements the subtyping rules between abstract heap types. It does not implement subtyping between index types (structs, arrays) and abstract heap types. Bug: 430198271 Change-Id: Id08d632fb21ca5e51753913b33e2efe800dbbfa5 Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/8446896 Commit-Queue: Matthias Liedtke <[email protected]> Reviewed-by: Manos Koukoutos <[email protected]>
1 parent 002ff92 commit 2813534

File tree

2 files changed

+161
-4
lines changed

2 files changed

+161
-4
lines changed

Sources/Fuzzilli/FuzzIL/TypeSystem.swift

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,7 +1332,10 @@ public class WasmTypeDefinition: WasmTypeExtension {
13321332
// TODO: Add continuation types for core stack switching.
13331333
// TODO: Add shared bit for shared-everything-threads.
13341334
// TODO: Add internal string type for JS string builtins.
1335-
enum WasmAbstractHeapType: CaseIterable {
1335+
enum WasmAbstractHeapType: CaseIterable, Comparable {
1336+
// Note: The union, intersection, ... implementations are inspired by Binaryen's implementation,
1337+
// so when extending the type system, feel free to use that implemenation as an orientation.
1338+
// https://github.com/WebAssembly/binaryen/blob/main/src/wasm/wasm-type.cpp
13361339
case WasmExtern
13371340
case WasmFunc
13381341
case WasmAny
@@ -1357,6 +1360,75 @@ enum WasmAbstractHeapType: CaseIterable {
13571360
return true
13581361
}
13591362
}
1363+
1364+
func isBottom() -> Bool {
1365+
getBottom() == self
1366+
}
1367+
1368+
func getBottom() -> Self {
1369+
switch self {
1370+
case .WasmExtern, .WasmNoExtern:
1371+
return .WasmNoExtern
1372+
case .WasmFunc, .WasmNoFunc:
1373+
return .WasmNoFunc
1374+
case .WasmAny, .WasmEq, .WasmI31, .WasmStruct, .WasmArray, .WasmNone:
1375+
return .WasmNone
1376+
case .WasmExn, .WasmNoExn:
1377+
return .WasmNoExn
1378+
}
1379+
}
1380+
1381+
func inSameHierarchy(_ other: Self) -> Bool {
1382+
return getBottom() == other.getBottom()
1383+
}
1384+
1385+
func union(_ other: Self) -> Self? {
1386+
if self == other {
1387+
return self
1388+
}
1389+
if !self.inSameHierarchy(other) {
1390+
return nil // Incompatible heap types.
1391+
}
1392+
if self.isBottom() {
1393+
return other
1394+
}
1395+
if other.isBottom() {
1396+
return self
1397+
}
1398+
// Let `a` be the lesser type.
1399+
let a = min(self, other)
1400+
let b = max(self, other)
1401+
return switch a {
1402+
case .WasmAny:
1403+
.WasmAny
1404+
case .WasmEq, .WasmI31, .WasmStruct:
1405+
.WasmEq
1406+
case .WasmArray:
1407+
.WasmAny
1408+
case .WasmExtern, .WasmFunc, .WasmExn, .WasmNone, .WasmNoExtern, .WasmNoFunc, .WasmNoExn:
1409+
fatalError("unhandled subtyping for a=\(a) b=\(b)")
1410+
}
1411+
}
1412+
1413+
func intersection(_ other: Self) -> Self? {
1414+
if self == other {
1415+
return self
1416+
}
1417+
if self.getBottom() != other.getBottom() {
1418+
return nil
1419+
}
1420+
if self.subsumes(other) {
1421+
return other
1422+
}
1423+
if other.subsumes(self) {
1424+
return self
1425+
}
1426+
return self.getBottom()
1427+
}
1428+
1429+
func subsumes(_ other: Self) -> Bool {
1430+
union(other) == self
1431+
}
13601432
}
13611433

13621434
// A wrapper around a WasmTypeDescription without owning the WasmTypeDescription.
@@ -1397,7 +1469,7 @@ public class WasmReferenceType: WasmTypeExtension {
13971469
case .Index(_):
13981470
return false
13991471
case .Abstract(let otherHeapType):
1400-
return heapType == otherHeapType
1472+
return heapType.subsumes(otherHeapType)
14011473
}
14021474
}
14031475
}
@@ -1448,7 +1520,10 @@ public class WasmReferenceType: WasmTypeExtension {
14481520
case .Index(_):
14491521
return nil
14501522
case .Abstract(let otherHeapType):
1451-
return heapType == otherHeapType ? WasmReferenceType(.Abstract(heapType), nullability: nullability) : nil
1523+
if let upperBound = heapType.union(otherHeapType) {
1524+
return WasmReferenceType(.Abstract(upperBound), nullability: nullability)
1525+
}
1526+
return nil
14521527
}
14531528
}
14541529
}
@@ -1472,7 +1547,10 @@ public class WasmReferenceType: WasmTypeExtension {
14721547
case .Index(_):
14731548
return nil
14741549
case .Abstract(let otherHeapType):
1475-
return heapType == otherHeapType ? WasmReferenceType(.Abstract(heapType), nullability: nullability) : nil
1550+
if let lowerBound = heapType.intersection(otherHeapType) {
1551+
return WasmReferenceType(.Abstract(lowerBound), nullability: nullability)
1552+
}
1553+
return nil
14761554
}
14771555
}
14781556
}

Tests/FuzzilliTests/TypeSystemTest.swift

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,85 @@ class TypeSystemTests: XCTestCase {
11831183
XCTAssertEqual(tagIntersection, .nothing)
11841184
}
11851185

1186+
func testWasmAbstractHeapTypeSubsumptionRules() {
1187+
let groupAny: [WasmAbstractHeapType] =
1188+
[.WasmAny, .WasmEq, .WasmI31, .WasmStruct, .WasmArray, .WasmNone]
1189+
let groupExtern: [WasmAbstractHeapType] = [.WasmExtern, .WasmNoExtern]
1190+
let groupFunc: [WasmAbstractHeapType] = [.WasmFunc, .WasmNoFunc]
1191+
let groupExn: [WasmAbstractHeapType] = [.WasmExn, .WasmNoExn]
1192+
let allGroups = [groupAny, groupExtern, groupFunc, groupExn]
1193+
let allTypes = allGroups.joined()
1194+
// If this fails, please extend the arrays above with the newly added type(s).
1195+
XCTAssert(WasmAbstractHeapType.allCases.allSatisfy(allTypes.contains))
1196+
1197+
// All types in the same type group share the same bottom type.
1198+
XCTAssert(groupAny.allSatisfy {$0.getBottom() == .WasmNone})
1199+
XCTAssert(groupExtern.allSatisfy {$0.getBottom() == .WasmNoExtern})
1200+
XCTAssert(groupFunc.allSatisfy {$0.getBottom() == .WasmNoFunc})
1201+
XCTAssert(groupExn.allSatisfy {$0.getBottom() == .WasmNoExn})
1202+
1203+
// The union and intersection of of two unrelated types are nil.
1204+
for groupA in allGroups {
1205+
for groupB in allGroups where groupA != groupB {
1206+
for typeA in groupA {
1207+
for typeB in groupB {
1208+
XCTAssertNil(typeA.union(typeB), "a=\(typeA) b=\(typeB)")
1209+
XCTAssertNil(typeA.intersection(typeB), "a=\(typeA) b=\(typeB)")
1210+
}
1211+
}
1212+
}
1213+
}
1214+
1215+
for type in allTypes {
1216+
XCTAssertEqual(type.union(type), type)
1217+
XCTAssertEqual(type.union(type.getBottom()), type)
1218+
XCTAssertEqual(type.getBottom().union(type), type)
1219+
XCTAssertEqual(type.intersection(type), type)
1220+
XCTAssertEqual(type.intersection(type.getBottom()), type.getBottom())
1221+
}
1222+
1223+
// Testing a few combinations.
1224+
XCTAssertEqual(WasmAbstractHeapType.WasmAny.union(.WasmEq), .WasmAny)
1225+
XCTAssertEqual(WasmAbstractHeapType.WasmStruct.union(.WasmArray), .WasmEq)
1226+
XCTAssertEqual(WasmAbstractHeapType.WasmI31.union(.WasmArray), .WasmEq)
1227+
XCTAssertEqual(WasmAbstractHeapType.WasmArray.union(.WasmEq), .WasmEq)
1228+
XCTAssertEqual(WasmAbstractHeapType.WasmArray.intersection(.WasmStruct), .WasmNone)
1229+
XCTAssertEqual(WasmAbstractHeapType.WasmI31.intersection(.WasmStruct), .WasmNone)
1230+
XCTAssertEqual(WasmAbstractHeapType.WasmI31.intersection(.WasmEq), .WasmI31)
1231+
XCTAssertEqual(WasmAbstractHeapType.WasmAny.intersection(.WasmArray), .WasmArray)
1232+
1233+
// Tests on the whole ILType.
1234+
let ref = {t in ILType.wasmRef(.Abstract(t), nullability: false)}
1235+
let refNull = {t in ILType.wasmRef(.Abstract(t), nullability: false)}
1236+
for type in allTypes {
1237+
let refT = ref(type)
1238+
let refNullT = refNull(type)
1239+
XCTAssertEqual(refT.union(with: refNullT), refNullT)
1240+
XCTAssertEqual(refNullT.union(with: refT), refNullT)
1241+
XCTAssertEqual(refT.union(with: refT), refT)
1242+
XCTAssertEqual(refNullT.union(with: refNullT), refNullT)
1243+
XCTAssertEqual(refT.intersection(with: refT), refT)
1244+
XCTAssertEqual(refNullT.intersection(with: refNullT), refNullT)
1245+
XCTAssertEqual(refT.intersection(with: refNullT), refT)
1246+
XCTAssertEqual(refNullT.intersection(with: refT), refT)
1247+
}
1248+
1249+
XCTAssertEqual(ref(.WasmAny).union(with: refNull(.WasmEq)), refNull(.WasmAny))
1250+
XCTAssertEqual(ref(.WasmStruct).union(with: ref(.WasmArray)), ref(.WasmEq))
1251+
// We should never do this for the type information of any Variable as .wasmGenericRef
1252+
// cannot be encoded in the Wasm module and any instruction that leads to such a static type
1253+
// is "broken". However, we will still need to allow this union type if we want to be able
1254+
// to request a .required(.wasmGenericRef) for operations like WasmRefIsNull.
1255+
XCTAssertEqual(ref(.WasmI31).union(with: refNull(.WasmExn)), .wasmGenericRef)
1256+
1257+
XCTAssertEqual(ref(.WasmAny).intersection(with: refNull(.WasmEq)), ref(.WasmEq))
1258+
XCTAssertEqual(refNull(.WasmI31).intersection(with: refNull(.WasmStruct)), refNull(.WasmNone))
1259+
// Note that `ref none` is a perfectly valid type in Wasm but such a reference can never be
1260+
// constructed.
1261+
XCTAssertEqual(ref(.WasmArray).intersection(with: refNull(.WasmStruct)), ref(.WasmNone))
1262+
XCTAssertEqual(refNull(.WasmArray).intersection(with: ref(.WasmAny)), ref(.WasmArray))
1263+
}
1264+
11861265
func testUnboundFunctionSubsumptionRules() {
11871266
XCTAssertEqual(ILType.unboundFunction(), .unboundFunction())
11881267
XCTAssertNotEqual(ILType.unboundFunction([] => .object()), .unboundFunction())

0 commit comments

Comments
 (0)