compiler: Index interned types by active fields

5db493faf3660904b5e1c56f08be422bdbcab4c32169ec63ca88b0f1e4991ffe
Alexis Sellier committed ago 1 parent 1de5c499
lib/std/lang/resolver.rad +66 -7
22 22
use std::lang::module;
23 23
24 24
/// Maximum number of diagnostics recorded.
25 25
export constant MAX_ERRORS: u32 = 64;
26 26
27 +
/// Power-of-two bucket count for interned type lookup chains.
28 +
constant TYPE_BUCKETS: u32 = 1024;
29 +
/// Odd multiplier that combines type cache key components.
30 +
constant CACHE_HASH_PRIME: u32 = 16777619;
31 +
27 32
/// Synthetic function name used when wrapping a bare expression for analysis.
28 33
export constant ANALYZE_EXPR_FN_NAME: *[u8] = "__expr__";
29 34
/// Synthetic function name used when wrapping a block for analysis.
30 35
export constant ANALYZE_BLOCK_FN_NAME: *[u8] = "__block__";
31 36
1085 1090
    config: Config,
1086 1091
    /// Caller-owned arena, valid for this resolver and all emitted metadata.
1087 1092
    arena: &'arena mut alloc::Arena,
1088 1093
    /// Combined semantic metadata table indexed by node ID.
1089 1094
    nodeData: NodeDataTable,
1090 -
    /// Linked list of interned types.
1091 -
    types: ?*TypeNode,
1095 +
    /// Lookup chains for interned types.
1096 +
    types: *unsafe mut [?*TypeNode],
1092 1097
    /// Diagnostics recorded so far.
1093 1098
    errors: DiagnosticBuffer,
1094 1099
    /// Stable module identities indexed by module ID.
1095 1100
    moduleEntries: [?*module::ModuleEntry; module::MAX_MODULES],
1096 1101
    /// Cache of module scopes indexed by module ID.
1108 1113
/// Internal error sentinel thrown when analysis cannot proceed.
1109 1114
export union ResolveError: Copy {
1110 1115
    Failure,
1111 1116
}
1112 1117
1113 -
/// Node in the type interning linked list.
1118 +
/// Node in a type interning lookup chain.
1114 1119
record TypeNode: Copy {
1120 +
    /// Exact interned type value.
1115 1121
    ty: Type,
1122 +
    /// Next type in the same lookup bucket.
1116 1123
    next: ?*TypeNode,
1117 1124
}
1118 1125
1119 1126
/// Look up a region name in a lexical environment.
1120 1127
unsafe fn findRegion(scope: ?*RegionScope, name: *[u8]) -> ?*unsafe mut types::Region {
1194 1201
    }
1195 1202
    set self.nodeData.entries[owner.id].extra = NodeExtra::Regions(frozen);
1196 1203
    return frozen;
1197 1204
}
1198 1205
1206 +
/// Mix aligned metadata addresses into a lookup key.
1207 +
fn addressHash(address: u64) -> u32 {
1208 +
    return ((address >> 3) as u32) ^ ((address >> 35) as u32);
1209 +
}
1210 +
1211 +
/// Hash the active fields of a pointer class.
1212 +
unsafe fn classHash(class: types::PointerClass) -> u32 {
1213 +
    match class {
1214 +
        case types::PointerClass::Owned => return 1,
1215 +
        case types::PointerClass::Ref => return 2,
1216 +
        case types::PointerClass::Unsafe => return 3,
1217 +
        case types::PointerClass::Region(region) => return addressHash(region as u64),
1218 +
    }
1219 +
}
1220 +
1221 +
/// Hash active type fields so equal values select the same lookup chain.
1222 +
unsafe fn typeHash(ty: Type) -> u32 {
1223 +
    match ty {
1224 +
        case Type::Cell { class, payload } =>
1225 +
            return addressHash(payload as u64) ^ (classHash(class) * CACHE_HASH_PRIME),
1226 +
        case Type::Pointer { class, target, mutable } =>
1227 +
            return addressHash(target as u64) ^ (classHash(class) * CACHE_HASH_PRIME) ^ (1 if mutable else 0),
1228 +
        case Type::Slice { class, item, mutable } =>
1229 +
            return addressHash(item as u64) ^ (classHash(class) * CACHE_HASH_PRIME) ^ (1 if mutable else 0),
1230 +
        case Type::TraitObject { class, traitInfo, mutable } =>
1231 +
            return addressHash(traitInfo as u64) ^ (classHash(class) * CACHE_HASH_PRIME) ^ (1 if mutable else 0),
1232 +
        case Type::Session(region) => return addressHash(region as u64),
1233 +
        case Type::Optional(inner) => return addressHash(inner as u64),
1234 +
        case Type::Fn(info) => return addressHash(info as u64),
1235 +
        case Type::Nominal(info) => return addressHash(info as u64),
1236 +
        case Type::Array(array) => return addressHash(array.item as u64) ^ (array.length * CACHE_HASH_PRIME),
1237 +
        case Type::Range { start, end } => {
1238 +
            let mut hash: u32 = 0;
1239 +
            if let item = start {
1240 +
                set hash = addressHash(item as u64);
1241 +
            }
1242 +
            if let item = end {
1243 +
                set hash = hash ^ (addressHash(item as u64) * CACHE_HASH_PRIME);
1244 +
            }
1245 +
            return hash;
1246 +
        }
1247 +
        else => return 0,
1248 +
    }
1249 +
}
1250 +
1199 1251
/// Allocate and intern a type in the arena, returning a pointer for deduplication.
1200 1252
export unsafe fn allocType 'arena (self: &mut Resolver 'arena, ty: Type) -> *Type {
1201 1253
    // Search existing types for a match.
1202 -
    let mut cursor = self.types;
1254 +
    let bucket = typeHash(ty) & (TYPE_BUCKETS - 1);
1255 +
    let mut cursor = self.types[bucket];
1203 1256
    while let node = cursor {
1204 1257
        if node.ty == ty {
1205 1258
            return &node.ty;
1206 1259
        }
1207 1260
        set cursor = node.next;
1209 1262
    // Allocate a new type node from the arena.
1210 1263
    let node = try! alloc::alloc(
1211 1264
        &mut *self.arena, @sizeOf(TypeNode), @alignOf(TypeNode)
1212 1265
    ) as *mut TypeNode;
1213 1266
1214 -
    set *node = TypeNode { ty, next: self.types };
1267 +
    set *node = TypeNode { ty, next: self.types[bucket] };
1215 1268
    let frozen: *TypeNode = node;
1216 -
    set self.types = frozen;
1269 +
    set self.types[bucket] = frozen;
1217 1270
1218 1271
    return &frozen.ty;
1219 1272
}
1220 1273
1221 1274
/// Allocate a nominal type descriptor and return a pointer to it.
1504 1557
    arena: &'arena mut alloc::Arena,
1505 1558
    storage: ResolverStorage,
1506 1559
    config: Config
1507 1560
) -> Resolver 'arena {
1508 1561
    let case ResolverStorage { nodeData, pkgScope, errors } = storage else panic "expected resolver storage";
1562 +
    let types = try! alloc::allocRawSlice(
1563 +
        arena, @sizeOf(?*TypeNode), @alignOf(?*TypeNode), TYPE_BUCKETS
1564 +
    ) as *unsafe mut [?*TypeNode];
1565 +
    for i in 0..types.len {
1566 +
        set types[i] = nil;
1567 +
    }
1509 1568
    let symbols = try! alloc::allocRawSlice(
1510 1569
        arena, @sizeOf(*unsafe mut Symbol), @alignOf(*unsafe mut Symbol), MAX_MODULE_SYMBOLS
1511 1570
    ) as *unsafe mut [*unsafe mut Symbol];
1512 1571
1513 1572
    // Initialize the root scope.
1553 1612
        currentMod: 0,
1554 1613
        inUnsafeContext: false,
1555 1614
        config,
1556 1615
        arena,
1557 1616
        nodeData: NodeDataTable { entries: nodeData },
1558 -
        types: nil,
1617 +
        types,
1559 1618
        errors: DiagnosticBuffer { entries: errors, len: 0 },
1560 1619
        moduleEntries: [nil; module::MAX_MODULES],
1561 1620
        moduleScopes,
1562 1621
        instances: undefined,
1563 1622
        instancesLen: 0,
lib/std/lang/resolver/tests.rad +42 -0
11 11
use std::lang::parser;
12 12
use std::lang::scanner;
13 13
use std::lang::module;
14 14
use std::lang::strings;
15 15
16 +
/// Interning preserves exact identity across populated collision chains.
17 +
@test unsafe fn testTypeInterningIdentity() throws (testing::TestError) {
18 +
    let mut arena = testArena();
19 +
    let storage: 'test = &mut arena in {
20 +
        let mut res = testResolver(storage);
21 +
        let item = super::allocType(&mut res, super::Type::U32);
22 +
        let mut entries: [?*super::Type; 2048] = [nil; 2048];
23 +
        for i in 0..entries.len {
24 +
            let ty = super::Type::Array(super::ArrayType { item, length: i });
25 +
            set entries[i] = super::allocType(&mut res, ty);
26 +
        }
27 +
        for i in 0..entries.len {
28 +
            let ty = super::Type::Array(super::ArrayType { item, length: i });
29 +
            let entry = entries[i] else throw testing::TestError::Failed;
30 +
            assert super::allocType(&mut res, ty) == entry;
31 +
            assert *entry == ty;
32 +
        }
33 +
        let values = [
34 +
            super::Type::U32, super::Type::U64,
35 +
            super::Type::Optional(item),
36 +
            super::Type::Range { start: nil, end: nil },
37 +
            super::Type::Range { start: item, end: nil },
38 +
            super::Type::Range { start: nil, end: item },
39 +
            super::Type::Range { start: item, end: item },
40 +
            super::Type::Cell { class: types::PointerClass::Ref, payload: item },
41 +
            super::Type::Pointer { class: types::PointerClass::Ref, target: item, mutable: false },
42 +
            super::Type::Pointer { class: types::PointerClass::Ref, target: item, mutable: true },
43 +
            super::Type::Pointer { class: types::PointerClass::Owned, target: item, mutable: false },
44 +
            super::Type::Pointer { class: types::PointerClass::Unsafe, target: item, mutable: false },
45 +
            super::Type::Slice { class: types::PointerClass::Ref, item, mutable: false },
46 +
            super::Type::Slice { class: types::PointerClass::Ref, item, mutable: true },
47 +
        ];
48 +
        for ty, i in values {
49 +
            let entry = super::allocType(&mut res, ty);
50 +
            assert super::allocType(&mut res, ty) == entry;
51 +
            for other, j in values {
52 +
                assert (entry == super::allocType(&mut res, other)) == (i == j);
53 +
            }
54 +
        }
55 +
    }
56 +
}
57 +
16 58
/// Array layouts retain element alignment for empty and populated arrays.
17 59
@test fn testArrayLayoutFromElementLayout() throws (testing::TestError) {
18 60
    for alignment in [1 as u32, 2, 4, 8, 16] {
19 61
        let item = super::Layout { size: alignment, alignment };
20 62
        for count in [0 as u32, 1, 3] {