compiler: Use stable symbol identities for safe lookups
186b07ed836cd3198dde3e35b3ed394255051ec3d53a308bf8fafac8783396d6
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); }", |