compiler: Use stable symbol identities for safe lookups

186b07ed836cd3198dde3e35b3ed394255051ec3d53a308bf8fafac8783396d6
Alexis Sellier committed ago 1 parent 95deb3c6
lib/std/lang/lower.rad +9 -9
335 335
    options: LowerOptions,
336 336
}
337 337
338 338
/// Entry mapping a function or data symbol to its emitted name.
339 339
record SymbolNameEntry: Copy {
340 -
    /// Stable resolver symbol identity.
341 -
    sym: *unsafe resolver::Symbol,
340 +
    /// Symbol identity within the lowering context's resolver.
341 +
    symbolId: u32,
342 342
    /// Qualified name in the emitted program.
343 343
    qualName: *[u8],
344 344
}
345 345
346 346
/// Entry in the global error tag table.
921 921
// Qualified Name Construction //
922 922
/////////////////////////////////
923 923
924 924
/// Get module path segments for the current or specified module.
925 925
/// Returns empty slice if no module graph or module not found.
926 -
unsafe fn getModulePath 'arena 'phase (self: &mut Lowerer 'arena 'phase, modId: ?u16) -> *[*[u8]] where 'arena: 'phase {
926 +
fn getModulePath 'arena 'phase (self: &Lowerer 'arena 'phase, modId: ?u16) -> *[*[u8]] where 'arena: 'phase {
927 927
    let graph = self.moduleGraph else {
928 928
        return &[];
929 929
    };
930 930
    let mut id = modId;
931 931
    if id == nil {
951 951
}
952 952
953 953
/// Register the emitted name for a function or data symbol.
954 954
/// Calls and address expressions use this name across package boundaries.
955 955
unsafe fn registerSymbolName 'arena 'phase (self: &mut Lowerer 'arena 'phase, sym: *unsafe resolver::Symbol, qualName: *[u8]) where 'arena: 'phase {
956 -
    self.symbolNames.append(SymbolNameEntry { sym, qualName }, alloc::arenaAllocator(self.arena));
956 +
    self.symbolNames.append(SymbolNameEntry { symbolId: sym.id, qualName }, alloc::arenaAllocator(self.arena));
957 957
}
958 958
959 959
/// Look up the emitted name for a function or data symbol.
960 960
/// Return `nil` if its declaration has not been lowered.
961 961
// TODO: This is kind of dubious as an optimization, if it depends on the order
962 962
// in which modules are lowered.
963 963
// TODO: Use a hash table here?
964 -
unsafe fn lookupSymbolName 'arena 'phase (self: &Lowerer 'arena 'phase, sym: *unsafe resolver::Symbol) -> ?*[u8] where 'arena: 'phase {
964 +
fn lookupSymbolName 'arena 'phase (self: &Lowerer 'arena 'phase, symbolId: u32) -> ?*[u8] where 'arena: 'phase {
965 965
    for entry in &self.symbolNames[..] {
966 -
        if entry.sym == sym {
966 +
        if entry.symbolId == symbolId {
967 967
            return entry.qualName;
968 968
        }
969 969
    }
970 970
    return nil;
971 971
}
1422 1422
    };
1423 1423
    if data.ty == resolver::Type::Unknown {
1424 1424
        throw LowerError::MissingType(node);
1425 1425
    }
1426 1426
    let layout = resolver::getTypeLayout(data.ty);
1427 -
    let qualName = lookupSymbolName(self, sym) else qualifyName(self, nil, sym.name);
1427 +
    let qualName = lookupSymbolName(self, sym.id) else qualifyName(self, nil, sym.name);
1428 1428
    let mut b = dataBuilder(alloc::arenaAllocator(self.arena));
1429 1429
    try lowerConstDataInto(self, value, data.ty, layout.size, qualName, &mut b);
1430 1430
    let result = dataBuilderFinish(b);
1431 1431
1432 1432
    self.data.append(il::Data {
2495 2495
2496 2496
/// Emit a copy instruction that loads a data symbol's address into a register.
2497 2497
unsafe fn emitDataAddr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sym: *unsafe resolver::Symbol) -> il::Reg where 'arena: 'phase, 'phase: 'function {
2498 2498
    let dst = nextReg(self);
2499 2499
    let modId = resolver::moduleIdForSymbol(self.low.resolver, sym);
2500 -
    let qualName = lookupSymbolName(self.low, sym) else qualifyName(self.low, modId, sym.name);
2500 +
    let qualName = lookupSymbolName(self.low, sym.id) else qualifyName(self.low, modId, sym.name);
2501 2501
2502 2502
    emit(self, il::Instr::Copy { dst, val: il::Val::DataSym(qualName) });
2503 2503
2504 2504
    return dst;
2505 2505
}
7269 7269
    if let sym = resolver::nodeData(self.low.resolver, callee).sym {
7270 7270
        if let case ast::NodeValue::FnDecl(_) = sym.node.value {
7271 7271
            // First try to look up the symbol in our registered functions.
7272 7272
            // This handles cross-package calls correctly, since packages are
7273 7273
            // lowered in dependency order.
7274 -
            if let qualName = lookupSymbolName(self.low, sym) {
7274 +
            if let qualName = lookupSymbolName(self.low, sym.id) {
7275 7275
                return il::Val::FnAddr(qualName);
7276 7276
            }
7277 7277
            // Fall back to computing the qualified name from the module graph.
7278 7278
            // This works for functions in the current package.
7279 7279
            let modId = resolver::moduleIdForSymbol(self.low.resolver, sym);
lib/std/lang/resolver.rad +32 -16
422 422
    Trait(*unsafe mut TraitType),
423 423
}
424 424
425 425
/// Resolved symbol allocated during semantic analysis.
426 426
export record Symbol: Copy {
427 +
    /// Unique identity within the resolver that created this symbol.
428 +
    id: u32,
427 429
    /// Symbol name in source code.
428 430
    name: *[u8],
429 431
    /// Data associated with the symbol.
430 432
    data: SymbolData,
431 433
    /// Bitset of attributes applied to the declaration.
895 897
    Place,
896 898
    /// Evaluate a place prefix after checking the complete place.
897 899
    Locate,
898 900
}
899 901
902 +
/// Symbol identity and storage retained for ownership diagnostics.
903 +
record TrackedSymbol: Copy {
904 +
    /// Resolver-local symbol identity.
905 +
    id: u32,
906 +
    /// Symbol storage used to report ownership errors.
907 +
    symbol: *unsafe mut Symbol,
908 +
}
909 +
900 910
/// Per-control-flow-path ownership state.
901 911
/// Read only the initialized symbol prefix below `len`.
902 912
record LinearEnv: Copy {
903 913
    /// Active full-region loans, indexed by the checker's regional loan table.
904 914
    regionalLoans: u64,
905 -
    /// Symbol pointers. Entries below `len` are initialized and not optional.
906 -
    symbols: [*unsafe mut Symbol; MAX_LINEAR_BINDINGS],
915 +
    /// Symbol identities and pointers. Entries below `len` are initialized.
916 +
    symbols: [TrackedSymbol; MAX_LINEAR_BINDINGS],
907 917
    /// Bit set for each binding that remains available.
908 918
    available: u64,
909 919
    /// Number of initialized entries in `symbols`.
910 920
    len: u32,
911 921
    /// Whether this control-flow path has terminated.
1013 1023
    next: ?*unsafe NominalApplication,
1014 1024
}
1015 1025
1016 1026
/// Global resolver state.
1017 1027
export record Resolver: 'arena {
1028 +
    /// Number of symbol identities allocated by this resolver.
1029 +
    symbolCount: u32,
1018 1030
    /// Active region names for source type checking.
1019 1031
    regionScope: ?*RegionScope,
1020 1032
    /// Interned applications of nominal region parameters.
1021 1033
    applications: ?*unsafe NominalApplication,
1022 1034
    /// Current scope.
1490 1502
    // TODO: Simplify.
1491 1503
    for i in 0..moduleScopes.len {
1492 1504
        set moduleScopes[i] = nil;
1493 1505
    }
1494 1506
    return Resolver 'arena {
1507 +
        symbolCount: 0,
1495 1508
        regionScope: nil,
1496 1509
        applications: nil,
1497 1510
        scope: pkgScope,
1498 1511
        pkgScope: pkgScope,
1499 1512
        loopStack: undefined,
1908 1921
}
1909 1922
1910 1923
/// Allocate a new symbol, and return a reference to it.
1911 1924
unsafe fn allocSymbol 'arena (self: &mut Resolver 'arena, data: SymbolData, name: *[u8], node: *ast::Node, attrs: u32) -> *unsafe mut Symbol {
1912 1925
    let sym = try! alloc::allocRaw(self.arena, @sizeOf(Symbol), @alignOf(Symbol)) as *unsafe mut Symbol;
1913 -
    set *sym = Symbol { name, data, attrs, node, moduleId: nil };
1926 +
    assert self.symbolCount < parser::U32_MAX, "allocSymbol: symbol identity overflow";
1927 +
    let id = self.symbolCount;
1928 +
    set self.symbolCount += 1;
1929 +
    set *sym = Symbol { id, name, data, attrs, node, moduleId: nil };
1914 1930
1915 1931
    return sym;
1916 1932
}
1917 1933
1918 1934
/// Check that a type is boolean, otherwise throw an error.
9863 9879
        try visitDecl(res, stmt);
9864 9880
    }
9865 9881
}
9866 9882
9867 9883
/// Find a tracked binding by symbol identity.
9868 -
unsafe fn findLinearBinding(env: &LinearEnv, sym: *unsafe mut Symbol) -> ?u32 {
9884 +
fn findLinearBinding(env: &LinearEnv, symbolId: u32) -> ?u32 {
9869 9885
    for i in 0..env.len {
9870 -
        if env.symbols[i] == sym {
9886 +
        if env.symbols[i].id == symbolId {
9871 9887
            return i;
9872 9888
        }
9873 9889
    }
9874 9890
    return nil;
9875 9891
}
9889 9905
        return;
9890 9906
    }
9891 9907
    if env.len >= MAX_LINEAR_BINDINGS {
9892 9908
        throw emitError(checker.resolver, node, ErrorKind::Internal);
9893 9909
    }
9894 -
    set env.symbols[env.len] = sym;
9910 +
    set env.symbols[env.len] = TrackedSymbol { id: sym.id, symbol: sym };
9895 9911
    set env.available |= (1 as u64) << (env.len as u64);
9896 9912
    set env.len += 1;
9897 9913
}
9898 9914
9899 9915
/// Mark a tracked binding as uninitialized.
9900 9916
unsafe fn markLinearBindingUnavailable 'arena (self: &mut Resolver 'arena, env: &mut LinearEnv, node: *ast::Node) {
9901 9917
    let sym = symbolFor(self, node) else return;
9902 -
    let index = findLinearBinding(env, sym) else return;
9918 +
    let index = findLinearBinding(env, sym.id) else return;
9903 9919
    set env.available &= ~((1 as u64) << (index as u64));
9904 9920
}
9905 9921
9906 9922
/// Require exact-use bindings introduced after `start` to be consumed.
9907 9923
unsafe fn finishLinearScope 'arena 'checking (
9910 9926
    start: u32,
9911 9927
) throws (ResolveError) where 'arena: 'checking {
9912 9928
    if not env.terminated {
9913 9929
        for i in start..env.len {
9914 9930
            if linearBindingAvailable(env, i) {
9915 -
                let sym = env.symbols[i];
9931 +
                let sym = env.symbols[i].symbol;
9916 9932
                let case SymbolData::Value { type: ty, .. } = sym.data
9917 9933
                    else panic "finishLinearScope: expected value symbol";
9918 9934
                if isLinear(ty) {
9919 9935
                    throw emitError(
9920 9936
                        checker.resolver,
9933 9949
    checker: &mut LinearChecker 'arena 'checking,
9934 9950
    env: &mut LinearEnv,
9935 9951
    node: *ast::Node,
9936 9952
) throws (ResolveError) where 'arena: 'checking {
9937 9953
    let sym = symbolFor(checker.resolver, node) else return;
9938 -
    let index = findLinearBinding(env, sym) else return;
9954 +
    let index = findLinearBinding(env, sym.id) else return;
9939 9955
    if not linearBindingAvailable(env, index) {
9940 9956
        let case SymbolData::Value { type: ty, .. } = sym.data
9941 9957
            else panic "consumeLinearIdent: expected value symbol";
9942 9958
        let kind = ErrorKind::LinearUseAfterConsume(sym.name) if isLinear(ty)
9943 9959
            else ErrorKind::AffineUseAfterMove(sym.name);
9951 9967
    env: &mut LinearEnv,
9952 9968
    node: *ast::Node,
9953 9969
) throws (ResolveError) where 'arena: 'checking {
9954 9970
    try checkLinearIdent(checker, env, node);
9955 9971
    let sym = symbolFor(checker.resolver, node) else return;
9956 -
    let index = findLinearBinding(env, sym) else return;
9972 +
    let index = findLinearBinding(env, sym.id) else return;
9957 9973
    set env.available &= ~((1 as u64) << (index as u64));
9958 9974
}
9959 9975
9960 9976
/// Merge ownership availability across two live branches.
9961 9977
/// Validate both inputs before writing to an output that can alias either input.
9981 9997
    }
9982 9998
    assert left.len == right.len, "joinLinearBranches: scope mismatch";
9983 9999
    let mut available = left.available;
9984 10000
    for i in 0..left.len {
9985 10001
        if linearBindingAvailable(left, i) <> linearBindingAvailable(right, i) {
9986 -
            let sym = left.symbols[i];
10002 +
            let sym = left.symbols[i].symbol;
9987 10003
            let case SymbolData::Value { type: ty, .. } = sym.data
9988 10004
                else panic "joinLinearBranches: expected value symbol";
9989 10005
            if isLinear(ty) {
9990 10006
                throw emitError(
9991 10007
                    checker.resolver,
10010 10026
    if env.terminated {
10011 10027
        return;
10012 10028
    }
10013 10029
    for i in 0..env.len {
10014 10030
        if linearBindingAvailable(env, i) {
10015 -
            let sym = env.symbols[i];
10031 +
            let sym = env.symbols[i].symbol;
10016 10032
            let case SymbolData::Value { type: ty, .. } = sym.data
10017 10033
                else panic "finishLinearExit: expected value symbol";
10018 10034
            if isLinear(ty) {
10019 10035
                throw emitError(
10020 10036
                    checker.resolver,
10498 10514
    let mark = checker.loopMarks[depth];
10499 10515
    let entryAvailable = checker.loopAvailable[depth];
10500 10516
    for i in 0..mark {
10501 10517
        let bit = (1 as u64) << (i as u64);
10502 10518
        if (env.available & bit) <> (entryAvailable & bit) {
10503 -
            let sym = env.symbols[i];
10519 +
            let sym = env.symbols[i].symbol;
10504 10520
            throw emitError(
10505 10521
                checker.resolver,
10506 10522
                node,
10507 10523
                ErrorKind::LinearBranchMismatch(sym.name),
10508 10524
            );
10532 10548
    if checker.loopHasNaturalExit[depth] or checker.loopBreakSeen[depth] {
10533 10549
        let expected = checker.loopExitAvailable[depth];
10534 10550
        for i in 0..mark {
10535 10551
            let bit = (1 as u64) << (i as u64);
10536 10552
            if (env.available & bit) <> (expected & bit) {
10537 -
                let sym = env.symbols[i];
10553 +
                let sym = env.symbols[i].symbol;
10538 10554
                throw emitError(
10539 10555
                    checker.resolver,
10540 10556
                    node,
10541 10557
                    ErrorKind::LinearBranchMismatch(sym.name),
10542 10558
                );
10631 10647
            }
10632 10648
            case ast::ProngArm::Else => {}
10633 10649
        }
10634 10650
        if prong.guard <> nil {
10635 10651
            for i in bindingsStart..branch.len {
10636 -
                let sym = branch.symbols[i];
10652 +
                let sym = branch.symbols[i].symbol;
10637 10653
                let case SymbolData::Value { type: ty, .. } = sym.data
10638 10654
                    else panic "checkLinearMatch: expected value symbol";
10639 10655
                if isLinear(ty) {
10640 10656
                    throw emitError(checker.resolver, prongNode, ErrorKind::LinearDiscard);
10641 10657
                }
11197 11213
            if let leftTy = typeFor(checker.resolver, assign.left) {
11198 11214
                if isMoveOnly(leftTy) {
11199 11215
                    set targetLinear = isLinear(leftTy);
11200 11216
                    if let case ast::NodeValue::Ident(_) = assign.left.value {
11201 11217
                        if let sym = symbolFor(checker.resolver, assign.left) {
11202 -
                            set target = findLinearBinding(env, sym);
11218 +
                            set target = findLinearBinding(env, sym.id);
11203 11219
                        }
11204 11220
                    }
11205 11221
                    if targetLinear and target == nil {
11206 11222
                        throw emitError(
11207 11223
                            checker.resolver,
lib/std/lang/resolver/tests/regions.rad +17 -0
2 2
3 3
use std::mem;
4 4
use std::testing;
5 5
use std::lang::types;
6 6
use std::lang::resolver;
7 +
use std::lang::ast;
8 +
9 +
/// Each declaration has a distinct identity within its resolver.
10 +
@test unsafe fn testSymbolIdentities() throws (testing::TestError) {
11 +
    let mut arena = super::testArena();
12 +
    let storage: 'test = &mut arena in {
13 +
        let mut res = super::testResolver(storage);
14 +
        let result = try super::resolveProgramStr(&mut res, "fn f() {} fn g() {}");
15 +
        try super::expectNoErrors(&result);
16 +
        let case ast::NodeValue::Block(block) = result.root.value else throw testing::TestError::Failed;
17 +
        let first = resolver::symbolFor(&res, block.statements[0]) else throw testing::TestError::Failed;
18 +
        let second = resolver::symbolFor(&res, block.statements[1]) else throw testing::TestError::Failed;
19 +
        assert first.id <> second.id;
20 +
        assert first.id < res.symbolCount;
21 +
        assert second.id < res.symbolCount;
22 +
    }
23 +
}
7 24
8 25
/// Short-circuit paths must agree on exact-use regional ownership.
9 26
@test unsafe fn testRegionalShortCircuitOwnership() throws (testing::TestError) {
10 27
    for program in [
11 28
        "union Ticket: 'r + Once { Value(&'r u32) } fn take 'r (t: Ticket 'r) -> bool { match t { case Ticket::Value(p) => return *p == 1, } } fn f 'r (t: Ticket 'r) { let result = false and take(t); }",