| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'bytecode_serialization.dart'; | |
| enum LocalVariableEntryKind { | |
| invalid, | |
| scope, | |
| variableDeclaration, | |
| contextVariable, | |
| } | |
| abstract class LocalVariableEntry { | |
| static const KindMask = 0x0F; | |
| static const IsCapturedFlag = 1 << 4; | |
| LocalVariableEntryKind get kind; | |
| int get flags => 0; | |
| final int startPC; | |
| void writeContents(BufferedWriter writer); | |
| LocalVariableEntry(this.startPC); | |
| } | |
| class Scope extends LocalVariableEntry { | |
| int? endPC; | |
| int contextLevel; | |
| int position; | |
| int? endPosition; | |
| final List<VariableDeclaration> variables = <VariableDeclaration>[]; | |
| Scope( | |
| int startPC, | |
| this.endPC, | |
| this.contextLevel, | |
| this.position, | |
| this.endPosition, | |
| ) : super(startPC); | |
| @override | |
| LocalVariableEntryKind get kind => LocalVariableEntryKind.scope; | |
| @override | |
| void writeContents(BufferedWriter writer) { | |
| final endPC = this.endPC; | |
| if (endPC == null) { | |
| throw '$this is not closed'; | |
| } | |
| writer.writePackedUInt30(endPC - startPC); | |
| writer.writeSLEB128(contextLevel); | |
| writer.writePackedUInt30(position + 1); | |
| writer.writePackedUInt30(endPosition! + 1); | |
| } | |
| Scope.readContents(BufferedReader reader, int startPC) | |
| : endPC = startPC + reader.readPackedUInt30(), | |
| contextLevel = reader.readSLEB128(), | |
| position = reader.readPackedUInt30() - 1, | |
| endPosition = reader.readPackedUInt30() - 1, | |
| super(startPC); | |
| @override | |
| String toString() => | |
| 'scope (pc $startPC-$endPC pos $position-$endPosition context-level $contextLevel)'; | |
| } | |
| class VariableDeclaration extends LocalVariableEntry { | |
| bool isCaptured; | |
| int index; | |
| int name; | |
| int type; | |
| int position; | |
| int initializedPosition; | |
| VariableDeclaration( | |
| int startPC, | |
| this.isCaptured, | |
| this.index, | |
| this.name, | |
| this.type, | |
| this.position, | |
| this.initializedPosition, | |
| ) : super(startPC); | |
| @override | |
| LocalVariableEntryKind get kind => LocalVariableEntryKind.variableDeclaration; | |
| @override | |
| int get flags => (isCaptured ? LocalVariableEntry.IsCapturedFlag : 0); | |
| @override | |
| void writeContents(BufferedWriter writer) { | |
| writer.writeSLEB128(index); | |
| writer.writePackedUInt30(name); | |
| writer.writePackedUInt30(type); | |
| writer.writePackedUInt30(position + 1); | |
| writer.writePackedUInt30(initializedPosition + 1); | |
| } | |
| VariableDeclaration.readContents( | |
| BufferedReader reader, | |
| int startPC, | |
| int flags, | |
| ) : isCaptured = (flags & LocalVariableEntry.IsCapturedFlag) != 0, | |
| index = reader.readSLEB128(), | |
| name = reader.readPackedUInt30(), | |
| type = reader.readPackedUInt30(), | |
| position = reader.readPackedUInt30() - 1, | |
| initializedPosition = reader.readPackedUInt30() - 1, | |
| super(startPC); | |
| @override | |
| String toString() => | |
| 'variable $index${isCaptured ? ' (captured)' : ''} pc $startPC, name CP#$name, type CP#$type, pos $position, init-pos $initializedPosition'; | |
| } | |
| class ContextVariable extends LocalVariableEntry { | |
| int index; | |
| ContextVariable(int startPC, this.index) : super(startPC); | |
| @override | |
| LocalVariableEntryKind get kind => LocalVariableEntryKind.contextVariable; | |
| @override | |
| void writeContents(BufferedWriter writer) { | |
| writer.writeSLEB128(index); | |
| } | |
| ContextVariable.readContents(BufferedReader reader, int startPC) | |
| : index = reader.readSLEB128(), | |
| super(startPC); | |
| @override | |
| String toString() => 'context variable $index'; | |
| } | |
| /// Keeps information about declared local variables. | |
| class LocalVariableTable extends BytecodeDeclaration { | |
| final scopes = <Scope>[]; | |
| final activeScopes = <Scope>[]; | |
| ContextVariable? contextVariable; | |
| LocalVariableTable(); | |
| void enterScope(int pc, int contextLevel, int position) { | |
| final scope = new Scope(pc, null, contextLevel, position, null); | |
| activeScopes.add(scope); | |
| scopes.add(scope); | |
| } | |
| void declareVariable( | |
| int pc, | |
| bool isCaptured, | |
| int index, | |
| int nameCpIndex, | |
| int typeCpIndex, | |
| int position, | |
| int initializedPosition, | |
| ) { | |
| final variable = new VariableDeclaration( | |
| pc, | |
| isCaptured, | |
| index, | |
| nameCpIndex, | |
| typeCpIndex, | |
| position, | |
| initializedPosition, | |
| ); | |
| activeScopes.last.variables.add(variable); | |
| } | |
| void leaveScope(int pc, int endPosition) { | |
| final scope = activeScopes.removeLast(); | |
| scope.endPC = pc; | |
| scope.endPosition = endPosition; | |
| if (scope.variables.isEmpty && | |
| activeScopes.isNotEmpty && | |
| scope.contextLevel == activeScopes.last.contextLevel) { | |
| scopes.remove(scope); | |
| } | |
| } | |
| void leaveAllScopes(int pc, int endPosition) { | |
| while (activeScopes.isNotEmpty) { | |
| leaveScope(pc, endPosition); | |
| } | |
| } | |
| void recordContextVariable(int pc, int index) { | |
| assert(contextVariable == null); | |
| contextVariable = new ContextVariable(pc, index); | |
| } | |
| bool get isEmpty => scopes.isEmpty && contextVariable == null; | |
| bool get isNotEmpty => !isEmpty; | |
| bool get hasActiveScopes => activeScopes.isNotEmpty; | |
| List<LocalVariableEntry> getEntries() { | |
| final entries = <LocalVariableEntry>[]; | |
| final contextVariable = this.contextVariable; | |
| if (contextVariable != null) { | |
| entries.add(contextVariable); | |
| } | |
| for (Scope scope in scopes) { | |
| entries.add(scope); | |
| entries.addAll(scope.variables); | |
| } | |
| return entries; | |
| } | |
| void write(BufferedWriter writer) { | |
| final entries = getEntries(); | |
| writer.writePackedUInt30(entries.length); | |
| final encodeStartPC = new SLEB128DeltaEncoder(); | |
| for (var entry in entries) { | |
| writer.writeByte(entry.kind.index | entry.flags); | |
| encodeStartPC.write(writer, entry.startPC); | |
| entry.writeContents(writer); | |
| } | |
| } | |
| LocalVariableTable.read(BufferedReader reader) { | |
| final int numEntries = reader.readPackedUInt30(); | |
| final decodeStartPC = new SLEB128DeltaDecoder(); | |
| late Scope scope; | |
| for (int i = 0; i < numEntries; ++i) { | |
| final int kindAndFlags = reader.readByte(); | |
| final LocalVariableEntryKind kind = LocalVariableEntryKind | |
| .values[kindAndFlags & LocalVariableEntry.KindMask]; | |
| final int flags = kindAndFlags & ~LocalVariableEntry.KindMask; | |
| final int startPC = decodeStartPC.read(reader); | |
| switch (kind) { | |
| case LocalVariableEntryKind.scope: | |
| scope = new Scope.readContents(reader, startPC); | |
| scopes.add(scope); | |
| break; | |
| case LocalVariableEntryKind.variableDeclaration: | |
| scope.variables.add( | |
| new VariableDeclaration.readContents(reader, startPC, flags), | |
| ); | |
| break; | |
| case LocalVariableEntryKind.contextVariable: | |
| contextVariable = new ContextVariable.readContents(reader, startPC); | |
| break; | |
| default: | |
| throw 'Unexpected entry kind ${kind}'; | |
| } | |
| } | |
| } | |
| Map<int, String> getBytecodeAnnotations() { | |
| final map = <int, String>{}; | |
| for (var entry in getEntries()) { | |
| final pc = entry.startPC; | |
| if (map[pc] == null) { | |
| map[pc] = entry.toString(); | |
| } else { | |
| map[pc] = "${map[pc]}; $entry"; | |
| } | |
| } | |
| return map; | |
| } | |
| } |