| # Dart Bytecode File Format |
| |
| ## Overview |
| |
| This file describes the binary format of Dart bytecode files. |
| |
| Dart bytecode is a binary representation of dynamic modules |
| on the VM/AOT. It is generated by dart2bytecode tools and |
| can be executed by the VM and AOT runtime (built with dynamic modules support). |
| |
| ## Basic types |
| |
| This document uses notation similar to [Dart kernel binary format](https://github.com/dart-lang/sdk/blob/main/pkg/kernel/binary.md). |
| |
| The follow basic data types are used to describe bytecode format. |
| ``` |
| type Byte = a byte |
| |
| type UInt = packed (variable-length) unsigned integer, same format as kernel binary |
| |
| type SLEB128 = packed (variable-length) signed integer |
| |
| type UInt32 = little-endian 32-bit unsigned integer |
| |
| type List<T> { |
| UInt length |
| T[length] items |
| } |
| |
| type Pair<T0, T1> { |
| T0 first |
| T1 second |
| } |
| |
| // Offset in the source file + 1. |
| type FileOffset = UInt |
| ``` |
| |
| ## Bytecode file |
| |
| Each bytecode file describes a dynamic module, a set of Dart libraries |
| which can be loaded at runtime. |
| |
| All declarations in the Dart bytecode file are sliced into several layers, |
| which reside in different sections such as libraries, classes, members, code, etc. |
| |
| ``` |
| type BytecodeFile { |
| UInt32 magic = 0x44424333; // 'DBC3' |
| UInt32 formatVersion = 1; |
| |
| // Descriptors of the sections below. |
| // Each section has a fixed index in the descriptors array. |
| Section[] sections; |
| |
| // Sections. The contents of the sections can occur in arbitrary |
| // order and with gaps between sections, so they should be located only via |
| // section descriptor. |
| |
| StringTable stringTable; |
| ObjectTable objectTable; |
| EntryPoint entryPoint; |
| LibraryUriAndOffset[] libraryIndex; |
| LibraryDeclaration[] libraries; |
| ClassDeclaration[] classes; |
| Members[] members; |
| Code[] codes; |
| SourcePositions[] sourcePositions; |
| SourceFile[] sourceFiles; |
| LineStarts[] lineStarts; |
| LocalVariables[] localVariables; |
| PackedObject[] annotations; |
| } |
| |
| type Section { |
| // Number of items in the section. |
| // Zero for string table, object table and entry point sections. |
| UInt32 numItems; |
| // Offset of the section relative to the beginning of the bytecode file, in bytes. |
| UInt32 offset; |
| } |
| ``` |
| |
| ### String table |
| |
| String table section contains strings encoded as 1-byte (Latin1) and 2-byte (UTF-16) characters. |
| Strings are not zero-terminated. |
| |
| This representation is aligned with Dart VM and allows VM to avoid extra copying and |
| decoding of string characters when loading a string. |
| |
| ``` |
| type StringTable = { |
| UInt32 numOneByteStrings; |
| UInt32 numTwoByteStrings; |
| // Offsets are relative to stringContents. |
| UInt32[numOneByteStrings] oneByteStringEndOffsets; |
| UInt32[numTwoByteStrings] twoByteStringEndOffsets; |
| // Contains string characters of one-byte strings and then two-byte strings. |
| Byte[] stringContents; |
| } |
| |
| // Reference to a string in string table. |
| type PackedString { |
| // Bit 0: set for two-byte string |
| // Bits 1+: index of string in one-byte strings (0..numOneByteStrings-1) |
| // or two-byte strings (0..numTwoByteStrings-1). |
| UInt indexAndKind; |
| } |
| ``` |
| |
| ### Object table |
| |
| Object table section contains various objects referenced by declarations and constant pools. |
| |
| Object included into the object table can be shared by multiple constant pools, |
| so object table provides a way to de-duplicate objects, reducing bytecode file |
| size and bytecode loading time. |
| |
| Bytecode generator can choose to include particular object into the object table or write it inline. |
| Object is written inline (not included into object table) if there are only a few references to that object or |
| it is small and can be created efficiently (for example, int and bool constants are always written inline). |
| |
| ``` |
| type ObjectTable { |
| UInt numEntries; |
| |
| // Total size of ‘objects’ in bytes. |
| UInt objectsSize; |
| |
| ObjectContents[numEntries] objects; |
| |
| // Offsets relative to ‘objects’. |
| UInt[numEntries] objectOffsets; |
| } |
| |
| |
| // Either reference to an object in the object table, or object contents |
| // written inline (determined by bit 0). |
| PackedObject = ObjectReference | ObjectContents; |
| |
| type ObjectReference { |
| // Bit 0 (reference bit): 1 |
| // Bits 1+: index in the object table |
| UInt reference; |
| } |
| |
| type ObjectContents { |
| // Bit 0 (reference bit): 0 |
| // Bits 1-4: object kind |
| // Bits 5+ object flags |
| UInt header; |
| } |
| |
| // Invalid/null object. Always present at index 0 of object table. |
| type InvalidObject extends ObjectContents { |
| kind = 0; |
| } |
| |
| type Library extends ObjectContents { |
| kind = 1; |
| PackedObject importUri; |
| } |
| |
| type Script extends ObjectContents { |
| kind = 2; |
| flags = (hasSourceFile); |
| PackedObject uri; |
| if hasSourceFile |
| // Offset of source file in ‘sourceFiles’ section of BytecodeFile. |
| UInt sourceFileOffset; |
| } |
| |
| type Class extends ObjectContents { |
| kind = 3; |
| PackedObject library; |
| // Empty name is used for artificial class containing top-level |
| // members of a library. |
| PackedObject name; |
| } |
| |
| type Member extends ObjectContents { |
| kind = 4; |
| flags = (isField, isConstructor); |
| PackedObject class; |
| PackedObject name; |
| } |
| |
| type Closure extends ObjectContents { |
| kind = 5; |
| PackedObject enclosingMember; |
| UInt closureIndex; |
| } |
| |
| type Name extends ObjectContents { |
| kind = 6; |
| flags = (isPublic); |
| |
| if !isPublic |
| PackedObject library; |
| |
| // Getters are prefixed with 'get:'. |
| // Setters are prefixed with 'set:'. |
| PackedString name; |
| } |
| |
| abstract type ConstObject extends ObjectContents { |
| kind = 7; |
| flags = constantTag (4 bits); |
| } |
| |
| type ConstInt extends ConstValue { |
| kind = 7; |
| constantTag = 1; |
| SLEB128 value; |
| } |
| |
| type ConstDouble extends ConstValue { |
| kind = 7; |
| constantTag = 2; |
| // double bits are reinterpreted as 64-bit int |
| SLEB128 value; |
| } |
| |
| type ConstBool extends ConstValue { |
| kind = 7; |
| constantTag = 3; |
| Byte isTrue; |
| } |
| |
| type ConstString extends ConstObject { |
| kind = 7; |
| constantTag = 4; |
| PackedString string; |
| } |
| |
| type ConstSymbol extends ConstObject { |
| kind = 7; |
| constantTag = 5; |
| PackedObject name; |
| } |
| |
| type ConstInstance extends ConstObject { |
| kind = 7; |
| constantTag = 6; |
| PackedObject type; |
| List<Pair<PackedObject, PackedObject>> fieldValues; |
| } |
| |
| type ConstList extends ConstObject { |
| kind = 7; |
| constantTag = 7; |
| PackedObject elementType; |
| List<PackedObject> elements; |
| } |
| |
| type ConstMap extends ConstObject { |
| kind = 7; |
| constantTag = 8; |
| // Map<K, V> type. |
| PackedObject mapType; |
| // [key1, value1, key2, value2, ....] |
| List<PackedObject> elements; |
| } |
| |
| type ConstSet extends ConstObject { |
| kind = 7; |
| constantTag = 9; |
| PackedObject elementType; |
| List<PackedObject> elements; |
| } |
| |
| type ConstRecord extends ConstObject { |
| kind = 7; |
| constantTag = 10; |
| PackedObject recordType; |
| // Values of positional, then named fields. |
| List<PackedObject> fieldValues; |
| } |
| |
| type ConstTearOff extends ConstObject { |
| kind = 7; |
| constantTag = 11; |
| PackedObject target; |
| } |
| |
| type ConstTearOffInstantiation extends ConstObject { |
| kind = 7; |
| constantTag = 12; |
| PackedObject tearOff; |
| PackedObject typeArguments; |
| } |
| |
| abstract type Type extends ObjectContents { |
| kind = 8; |
| flags = (typeTag (4 bits), isNullable); |
| } |
| |
| type DynamicType extends Type { |
| kind = 8; |
| typeTag = 1; |
| } |
| |
| type VoidType extends Type { |
| kind = 8; |
| typeTag = 2; |
| } |
| |
| type NullType extends Type { |
| kind = 8; |
| typeTag = 3; |
| } |
| |
| type NeverType extends Type { |
| kind = 8; |
| typeTag = 4; |
| } |
| |
| // Interface type without type arguments. |
| type SimpleType extends Type { |
| kind = 8; |
| typeTag = 5; |
| PackedObject class; |
| } |
| |
| // Generic interface type. |
| type GenericType extends Type { |
| kind = 8; |
| typeTag = 6; |
| PackedObject class; |
| PackedObject typeArguments; |
| } |
| |
| type TypeParameter extends Type { |
| kind = 8; |
| typeTag = 7; |
| // Class, Member or Closure declaring this type parameter. |
| // Null (Invalid) if declared by function type. |
| PackedObject parent; |
| // For type parameters declared by function types this index is in a concatenation |
| // of type parameters of all enclosing function types. |
| UInt indexInParent; |
| } |
| |
| type FunctionType extends Type { |
| kind = 8; |
| typeTag = 8; |
| |
| UInt functionTypeFlags = (hasOptionalPositionalParams, |
| hasOptionalNamedParams, |
| hasTypeParams, |
| hasEnclosingTypeParameters, |
| hasParameterFlags); |
| |
| if hasEnclosingTypeParameters |
| UInt numEnclosingTypeParameters; |
| |
| if hasTypeParams |
| TypeParametersDeclaration typeParameters; |
| |
| UInt numParameters; |
| |
| if hasOptionalPositionalParams || hasOptionalNamedParams |
| UInt numRequiredParameters; |
| |
| Type[] positionalParameters; |
| NameAndType[] namedParameters; |
| |
| if hasParameterFlags |
| // For named parameters: (isRequired) |
| List<UInt> parameterFlags; |
| |
| PackedObject returnType; |
| } |
| |
| type TypeParametersDeclaration { |
| UInt numTypeParameters; |
| PackedObject[numTypeParameters] names; |
| BoundAndDefaultType[numTypeParameters] boundsAndDefaultTypes; |
| } |
| |
| type BoundAndDefaultType { |
| PackedObject bound; |
| PackedObject defaultType; |
| } |
| |
| type NameAndType { |
| PackedObject name; |
| PackedObject type; |
| } |
| |
| type RecordType extends Type { |
| kind = 8; |
| typeTag = 9; |
| UInt numPositionalFields; |
| UInt numNamedFields; |
| Type[numPositionalFields] positionalFields; |
| NameAndType[numNamedFields] namedFields; |
| } |
| |
| // Type arguments vector. |
| type TypeArguments extends ObjectContents { |
| kind = 6; |
| List<PackedObject> args; |
| } |
| |
| // Describes shape of arguments passed to a call. |
| type ArgDesc extends ObjectContents { |
| kind = 8; |
| flags = (hasNamedArgs, hasTypeArgs); |
| |
| UInt numArguments; |
| |
| if hasTypeArgs |
| UInt numTypeArguments; |
| |
| if hasNamedArgs |
| List<PackedObject> argNames; |
| } |
| ``` |
| |
| ### Entry point |
| |
| Entry point section contains a reference to a dynamic module entry point function. |
| |
| ``` |
| type EntryPoint { |
| PackedObject member; |
| } |
| ``` |
| |
| ### Library index |
| |
| Library index section contains the list of library "forward declarations" |
| in the form of library URI and offset of its full declaration. |
| |
| ``` |
| type LibraryUriAndOffset { |
| PackedObject uri; |
| |
| // Offset of library declaration in the ‘libraries’ section of BytecodeFile. |
| UInt libraryOffset |
| } |
| ``` |
| |
| ### Libraries |
| |
| Libraries section contains declarations of libraries included into a dynamic module. |
| Number of items in the library index section and libraries section should be the same. |
| |
| ``` |
| type LibraryDeclaration { |
| UInt flags = (usesDartMirrors, usesDartFfi); |
| PackedObject name; |
| PackedObject script; |
| |
| // Classes declared in this library. |
| // The first class is an artificial ‘top-level’ class containing top-level |
| // fields and functions, as well as library annotations. |
| List<ClassNameAndOffset> classes; |
| } |
| |
| type ClassNameAndOffset { |
| PackedObject className; |
| |
| // Offset of class declaration in the ‘classes’ section of BytecodeFile. |
| UInt classOffset; |
| } |
| ``` |
| |
| ### Classes |
| |
| Classes section contains declarations of classes included into a dynamic module. |
| Each class in the classes section should be mentioned in its library declaration. |
| |
| ``` |
| type ClassDeclaration { |
| UInt flags = (isAbstract, isEnum, |
| hasTypeParams, hasTypeArguments, |
| isTransformedMixinApplication, |
| hasSourcePositions, hasAnnotations, hasPragma, |
| hasConstConstructor, isSealed, isMixinClass, |
| isBaseClass, isInterface, isFinal); |
| PackedObject script; |
| |
| if hasSourcePositions |
| FileOffset position; |
| FileOffset endPosition; |
| |
| if hasTypeArguments |
| UInt numTypeArguments; |
| if hasTypeParams |
| TypeParametersDeclaration typeParameters; |
| |
| PackedObject superType; |
| List<PackedObject> interfaces; |
| |
| if hasAnnotations |
| UInt annotationsOffset; |
| |
| // Offset of class members in the ‘members’ section of BytecodeFile. |
| UInt membersOffset; |
| } |
| ``` |
| |
| ### Members |
| |
| Members section contains declaration of members (fields, methods and constructors). |
| Member declarations are organized per class. |
| |
| ``` |
| type Members { |
| // Total number of functions, including field getters and setters. |
| UInt numFunctions; |
| |
| List<FieldDeclaration> fields; |
| List<FunctionDeclaration> functions; |
| } |
| |
| type FieldDeclaration { |
| UInt flags = (isStatic, isConst, isFinal, isLate, |
| isCovariant, isCovariantByClass, |
| isExtensionMember, isReflectable, |
| hasGetter, hasSetter, |
| hasInitializer, hasNontrivialInitializer, hasInitializerCode, |
| hasSourcePositions, hasAnnotations, hasPragma, |
| hasCustomScript); |
| PackedObject name; |
| PackedObject type; |
| |
| if hasCustomScript |
| PackedObject script; |
| if hasSourcePositions |
| FileOffset position; |
| FileOffset endPosition; |
| |
| if hasInitializerCode |
| // Offset of initializer code in the ‘code’ section of BytecodeFile. |
| UInt initializerCodeOffset; |
| if !hasNontrivialInitializer |
| PackedObject value; |
| |
| if hasGetter |
| PackedObject getterName; |
| if hasSetter |
| PackedObject setterName; |
| if hasAnnotations |
| UInt annotationsOffset; |
| } |
| |
| type FunctionDeclaration { |
| UInt flags = (isStatic, isAbstract, isGetter, isSetter, |
| isConstructor, isFactory, isConst, |
| hasOptionalPositionalParams, hasOptionalNamedParams, |
| hasTypeParams, hasParameterFlags, |
| isExtensionMember, isReflectable, isDebuggable, |
| isAsync, isAsyncStar, isSyncStar, |
| isNoSuchMethodForwarder, isExternal, isNative, |
| hasSourcePositions, hasAnnotations, hasPragma, |
| hasCustomScript); |
| |
| PackedObject name; |
| |
| if hasCustomScript |
| PackedObject script; |
| if hasSourcePositions |
| FileOffset position; |
| FileOffset endPosition; |
| |
| if hasTypeParams |
| TypeParametersDeclaration typeParameters; |
| |
| UInt numParameters; |
| |
| if hasOptionalPositionalParams || hasOptionalNamedParams |
| UInt numRequiredParameters; |
| |
| NameAndType[numParameters] parameters; |
| if hasParameterFlags |
| // For named parameters: (isRequired) |
| List<UInt> parameterFlags; |
| |
| PackedObject returnType; |
| |
| if isNative |
| PackedObject nativeName; |
| |
| if !isAbstract |
| // Offset of code in the ‘code’ section of BytecodeFile. |
| UInt codeOffset; |
| |
| if hasAnnotations |
| // Offset of function annotations in ‘annotations’ section of |
| // component, followed by parameter annotations. |
| UInt annotationsOffset; |
| } |
| ``` |
| |
| ### Code |
| |
| Code section contains bodies of members (including field initializers). |
| |
| ``` |
| type Code { |
| UInt flags = (hasExceptionsTable, hasSourcePositions, hasNullableFields, |
| hasClosures, hasParameterFlags, hasForwardingStubTarget, |
| hasDefaultFunctionTypeArgs, hasLocalVariables) |
| |
| if hasParameterFlags |
| // For all parameters: (isCovariant, isCovariantByClass) |
| List<UInt> flags; |
| |
| if hasForwardingStubTarget |
| ConstantIndex forwardingStubTarget; |
| |
| if hasDefaultFunctionTypeArgs |
| ConstantIndex defaultFunctionTypeArgs; |
| |
| if hasClosures |
| List<ClosureDeclaration> closureDeclarations; |
| |
| ConstantPool constantPool; |
| |
| UInt bytecodeSizeInBytes |
| Byte[bytecodeSizeInBytes] bytecodes; |
| |
| if hasExceptionsTable |
| ExceptionsTable exceptionsTable; |
| |
| if hasSourcePositions |
| // Offset of SourcePositions in ‘sourcePositions’ section of BytecodeFile. |
| UInt sourcePositionsOffset; |
| |
| if hasLocalVariables |
| // Offset of LocalVariables in ‘localVariables’ section of BytecodeFile. |
| UInt localVariablesOffset; |
| |
| if hasNullableFields |
| List<PackedObject> nullableFields; |
| |
| if hasClosures |
| // Parallel to closureDeclarations. |
| ClosureCode[] closures; |
| } |
| |
| type ClosureDeclaration { |
| UInt flags = (hasOptionalPositionalParams, hasOptionalNamedParams, |
| hasTypeParams, hasSourcePositions, |
| isAsync, isAsyncStar, isSyncStar, isDebuggable, |
| hasParameterFlags) |
| |
| // Member or Closure. |
| PackedObject parent; |
| PackedObject name; |
| |
| if hasSourcePositions |
| FileOffset position; |
| FileOffset endPosition; |
| |
| if hasTypeParams |
| TypeParametersDeclaration typeParameters; |
| |
| UInt numParameters; |
| |
| if hasOptionalPositionalParams || hasOptionalNamedParams |
| UInt numRequiredParameters; |
| |
| NameAndType[numParameters] parameters; |
| |
| if hasParameterFlags |
| // For named parameters: (isRequired) |
| List<UInt> parameterFlags; |
| |
| PackedObject returnType; |
| } |
| |
| type ClosureCode { |
| UInt flags = (hasExceptionsTable, hasSourcePositions, hasLocalVariables) |
| |
| UInt bytecodeSizeInBytes; |
| Byte[bytecodeSizeInBytes] bytecodes; |
| |
| if hasExceptionsTable |
| ExceptionsTable exceptionsTable; |
| |
| if hasSourcePositions |
| // Offset of SourcePositions in ‘sourcePositions’ section of BytecodeFile. |
| UInt sourcePositionsOffset; |
| |
| if hasLocalVariables |
| // Offset of LocalVariables in ‘localVariables’ section of BytecodeFile. |
| UInt localVariablesOffset; |
| } |
| ``` |
| |
| ### Constant Pool |
| |
| Each member code has its own constant pool which describes an array of objects |
| used by the interpreter at runtime along with bytecode instructions. |
| Member constant pool is shared by all closure functions declared in the member. |
| Bytecode instructions reference constant pool entries by index. |
| |
| ``` |
| type ConstantPool { |
| List<ConstantPoolEntry> entries; |
| } |
| |
| // Index of entry in the constant pool. |
| type ConstantIndex = UInt; |
| |
| abstract type ConstantPoolEntry { |
| Byte tag; |
| } |
| |
| type ConstantObjectRef extends ConstantPoolEntry { |
| Byte tag = 1; |
| PackedObject object; |
| } |
| |
| type ConstantClass extends ConstantPoolEntry { |
| Byte tag = 2; |
| PackedObject class; |
| } |
| |
| type ConstantType extends ConstantPoolEntry { |
| Byte tag = 3; |
| PackedObject type; |
| } |
| |
| type ConstantStaticField extends ConstantPoolEntry { |
| Byte tag = 4; |
| PackedObject field; |
| } |
| |
| // Occupies 2 entries in the constant pool. |
| // (to hold offset and field object). |
| type ConstantInstanceField extends ConstantPoolEntry { |
| Byte tag = 5; |
| PackedObject field; |
| } |
| |
| type ConstantTypeArgumentsField extends ConstantPoolEntry { |
| Byte tag = 6; |
| PackedObject class; |
| } |
| |
| type ConstantClosureFunction extends ConstantPoolEntry { |
| Byte tag = 7; |
| UInt closureIndex; |
| } |
| |
| type ConstantEndClosureFunctionScope extends ConstantPoolEntry { |
| Byte tag = 8; |
| } |
| |
| type ConstantSubtypeTestCache extends ConstantPoolEntry { |
| Byte tag = 9; |
| } |
| |
| type ConstantEmptyTypeArguments extends ConstantPoolEntry { |
| Byte tag = 10; |
| } |
| |
| // Occupies 2 entries in the constant pool |
| // (to hold target and arguments descriptor). |
| type ConstantDirectCall extends ConstantPoolEntry { |
| Byte tag = 11; |
| PackedObject target; |
| PackedObject argDesc; |
| } |
| |
| // Occupies 2 entries in the constant pool |
| // (to hold interface target and arguments descriptor). |
| type ConstantInterfaceCall extends ConstantPoolEntry { |
| Byte tag = 12; |
| PackedObject target; |
| PackedObject argDesc; |
| } |
| |
| // Occupies 3 entries in the constant pool. |
| type ConstantInstantiatedInterfaceCall extends ConstantPoolEntry { |
| Byte tag = 13; |
| PackedObject target; |
| PackedObject argDesc; |
| PackedObject staticReceiverType; |
| } |
| |
| // Occupies 2 entries in the constant pool |
| type ConstantDynamicCall extends ConstantPoolEntry { |
| Byte tag = 14; |
| PackedObject selectorName; |
| PackedObject argDesc; |
| } |
| |
| // Occupies 2 entries in the constant pool |
| type ConstantExternalCall extends ConstantPoolEntry { |
| Byte tag = 15; |
| } |
| ``` |
| |
| ### Exceptions table |
| |
| Each Code and ClosureCode may have an optional table of try blocks |
| which maps ranges of bytecode instructions to corresponding exception handlers. |
| |
| ``` |
| // Offset of a bytecode instruction in 'bytecodes'. |
| type BytecodeOffset = UInt; |
| |
| type TryBlock { |
| UInt outerTryIndexPlus1; |
| BytecodeOffset startPC; // Inclusive. |
| BytecodeOffset endPC; // Exclusive. |
| BytecodeOffset handlerPC; |
| Byte flags = (needsStackTrace, isSynthetic); |
| List<ConstantIndex> types; |
| } |
| |
| type ExceptionsTable { |
| // Ordered by startPC, then by nesting (outer precedes inner). |
| // Try blocks are properly nested. It means there are no partially |
| // overlapping try blocks - each pair of try block regions either |
| // has no intersection or one try block region encloses another. |
| List<TryBlock> tryBlocks; |
| } |
| ``` |
| |
| ### Source positions |
| |
| Source positions information maps ranges of bytecode instructions to |
| corresponding source positions. This information is optional and resides in |
| the 'sourcePositions' section of BytecodeFile. |
| |
| ``` |
| type SourcePositions { |
| UInt numEntries; |
| // Encoded list of pairs (PC, FileOffset), ordered by PC (offset of bytecode instruction). |
| // For two consecutive entries (PC1, FileOffset1), (PC2, FileOffset2) all |
| // bytecode instructions in range [PC1,PC2) correspond to a source |
| // position FileOffset1. |
| SourcePositionEntry[numEntries] entries |
| } |
| |
| type SourcePositionEntry = { |
| // Delta-encoded PC (delta from the PC of previous entry). |
| UInt PCDelta; |
| // Delta-encoded FileOffset (delta from the FileOffset of previous entry). |
| SLEB128 FileOffsetDelta; |
| } |
| ``` |
| |
| ### Source files and line starts |
| |
| ``` |
| type SourceFile { |
| UInt flags = (hasLineStarts, hasSource) |
| PackedObject importUri; |
| |
| if hasLineStarts |
| // Offset of line starts in ‘lineStarts’ section of BytecodeFile. |
| UInt lineStartsOffset; |
| |
| if hasSource |
| PackedString source; |
| } |
| |
| type LineStarts { |
| // Can be also treated as line lengths. |
| List<UInt> deltaEncodedLineStarts; |
| } |
| ``` |
| |
| ### Local variables |
| |
| ``` |
| type LocalVariables { |
| UInt numEntries; |
| // Encoded list of entries ordered by PC (offset of bytecode instruction). |
| LocalVariableEntry[numEntries] entries; |
| } |
| |
| abstract type LocalVariableEntry = { |
| // Bits 0-3: kind |
| // Bits 4-7: flags |
| Byte kindAndFlags; |
| // Delta-encoded start PC. |
| SLEB128 startPCDelta; |
| } |
| |
| type Scope extends LocalVariableEntry { |
| kind = 1 |
| // Offset to the end PC relative to startPC. |
| UInt endPCDelta; |
| // Level of context containing captured variables in this scope. |
| // -1 if there is no context. |
| SLEB128 contextLevel; |
| FileOffset position; |
| FileOffset endPosition; |
| } |
| |
| // Variable declarations immediately follow their corresponding Scope |
| type VariableDeclaration extends LocalVariableEntry { |
| kind = 2 |
| flags = (isCaptured) |
| // Index of local variable in the stack frame or in the context. |
| SLEB128 index; |
| ConstantIndex name; |
| ConstantIndex type; |
| // Source position where variable is declared. |
| FileOffset position; |
| // Source position after variable is initialized. |
| FileOffset initializedPosition; |
| } |
| |
| type ContextVariable extends LocalVariableEntry { |
| kind = 3 |
| // Index of a local variable holding context (in the stack frame). |
| SLEB128 index; |
| } |
| ``` |
| |
| ## Bytecode instructions |
| |
| ### Execution state |
| |
| Dart bytecode interpreter is a stack machine with local variables. |
| |
| Bytecode instructions should begin by creating a frame using |
| `Entry`, `EntryOptional` or `EntrySuspendable` instruction. |
| |
| After creating a frame, local variables and parameters can be referenced as `Locals[i]` up to the size of the frame. |
| For frames created with `Entry` instruction, parameter n is accessible via `Locals[-numParams-kParamEndSlotFromFp+n]` |
| (where `kParamEndSlotFromFp == 4`). For frames created with `EntryOptional` or `EntrySuspendable` instructions, |
| parameter n is copied to `Locals[n]`. |
| |
| Most bytecode instructions take values from the expression stack or push values onto the stack. |
| Stack and local variables are disjoint, e.g. instructions operating with locals cannot be used to access stack and vice versa. |
| Stack pointer is referred as `SP`, stack grows up. `SP[0]` is the value on top of the stack, `SP[-1]`, `SP[-2]` etc are values pushed previously. |
| Stack can be used after frame setup and initially empty. |
| |
| Data flow merges are not allowed on the stack: at each PC and stack location, |
| there should be exactly one bytecode instructions which could have pushed a value into that stack location, |
| regardless of how control reached that PC. |
| |
| Bytecode instructions can reference constant pool via `ConstantPool[i]`. |
| |
| ### Instruction encoding |
| |
| Each instruction starts with opcode byte. Certain instructions have |
| wide encoding variant. In such case, the least significant bit of opcode is |
| 0 for compact variant and 1 for wide variant. |
| |
| The following operand encodings are used: |
| |
| ``` |
| 0........8.......16.......24.......32.......40.......48 |
| +--------+ |
| | opcode | 0: no operands |
| +--------+ |
| |
| +--------+--------+ |
| | opcode | A | A: unsigned 8-bit operand |
| +--------+--------+ |
| |
| +--------+--------+ |
| | opcode | D | D: unsigned 8/32-bit operand |
| +--------+--------+ |
| |
| +--------+----------------------------------+ |
| | opcode | D | D (wide) |
| +--------+----------------------------------+ |
| |
| +--------+--------+ |
| | opcode | X | X: signed 8/32-bit operand |
| +--------+--------+ |
| |
| +--------+----------------------------------+ |
| | opcode | X | X (wide) |
| +--------+----------------------------------+ |
| |
| +--------+--------+ |
| | opcode | T | T: signed 8/24-bit operand |
| +--------+--------+ |
| |
| +--------+--------------------------+ |
| | opcode | T | T (wide) |
| +--------+--------------------------+ |
| |
| +--------+--------+--------+ |
| | opcode | A | E | A_E: unsigned 8-bit operand and |
| +--------+--------+--------+ unsigned 8/32-bit operand |
| |
| +--------+--------+----------------------------------+ |
| | opcode | A | E | A_E (wide) |
| +--------+--------+----------------------------------+ |
| |
| +--------+--------+--------+ |
| | opcode | A | Y | A_Y: unsigned 8-bit operand and |
| +--------+--------+--------+ signed 8/32-bit operand |
| |
| +--------+--------+----------------------------------+ |
| | opcode | A | Y | A_Y (wide) |
| +--------+--------+----------------------------------+ |
| |
| +--------+--------+--------+ |
| | opcode | D | F | D_F: unsigned 8/32-bit operand and |
| +--------+--------+--------+ unsigned 8-bit operand |
| |
| +--------+----------------------------------+--------+ |
| | opcode | D | F | D_F (wide) |
| +--------+----------------------------------+--------+ |
| |
| +--------+--------+--------+--------+ |
| | opcode | A | B | C | A_B_C: 3 unsigned 8-bit operands |
| +--------+--------+--------+--------+ |
| ``` |
| |
| ### Instruction Set |
| |
| #### Trap |
| |
| Unreachable instruction. |
| |
| #### Entry D |
| |
| Function prologue for the function. |
| D - number of local slots to reserve. |
| |
| #### EntryOptional A, B, C |
| |
| Function prologue for the function with optional or named arguments. |
| A - expected number of positional arguments. |
| B - number of optional positional arguments. |
| C - number of named arguments. |
| |
| Only one of B and C can be not 0. |
| |
| If B is not 0 then EntryOptional bytecode is followed by B LoadConstant |
| bytecodes specifying default values for optional positional arguments. |
| |
| If C is not 0 then EntryOptional is followed by 2 * C LoadConstant |
| bytecodes. |
| Bytecode at 2 * i specifies name of the i-th named argument and at |
| 2 * i + 1 default value. ‘A’ operand of the LoadConstant bytecode specifies |
| the location of the parameter on the stack. Named arguments are |
| sorted alphabetically. |
| |
| Note: Unlike Entry bytecode EntryOptional does not setup the frame for |
| local variables. This is done by a separate bytecode Frame, which should |
| follow EntryOptional and its LoadConstant instructions. |
| |
| #### EntrySuspendable A, B, C |
| |
| Similar to EntryOptional, but also reserves a local variable slot |
| for suspend state variable. |
| |
| #### LoadConstant A, E |
| |
| Used in conjunction with EntryOptional and EntrySuspendable instructions to |
| describe names and default values of optional parameters. |
| |
| #### Frame D |
| |
| Reserve space for D local variables. Local variables are |
| initially initialized with null. |
| |
| #### CheckFunctionTypeArgs A, E |
| |
| Check for a passed-in type argument vector of length A and |
| store it at Locals[E]. |
| |
| #### CheckStack A |
| |
| Compare SP against stack limit and call stack overflow or interrupt handler if |
| necessary. Should be used in prologue (A = 0), or at the beginning of |
| a loop with depth A. |
| |
| #### Allocate D |
| |
| Allocate object of class ConstantPool[D] with no type arguments. |
| |
| #### AllocateT |
| |
| Allocate object of class SP[0] with type arguments SP[-1]. |
| |
| #### CreateArrayTOS |
| |
| Allocate array of length SP[0] with type arguments SP[-1]. |
| |
| #### AllocateContext A, E |
| |
| Allocate Context object holding E context variables. |
| A is a static ID of the context. Static ID of a context may be used to |
| disambiguate accesses to different context objects. |
| Context objects with the same ID should have the same number of |
| context variables. |
| |
| #### CloneContext A, E |
| |
| Clone Context object SP[0] holding E context variables. |
| A is a static ID of the context. Cloned context has the same ID. |
| |
| #### LoadContextParent |
| |
| Load parent from context SP[0]. |
| |
| #### StoreContextParent |
| |
| Store context SP[0] into `parent` field of context SP[-1]. |
| |
| #### LoadContextVar A, E |
| |
| Load value from context SP[0] at index E. |
| A is a static ID of the context. |
| |
| #### StoreContextVar A, E |
| |
| Store value SP[0] into context SP[-1] at index E. |
| A is a static ID of the context. |
| |
| #### PushConstant D |
| |
| Push value ConstantPool[D] onto the stack. |
| |
| #### PushNull |
| |
| Push `null` onto the stack. |
| |
| #### PushTrue |
| |
| Push `true` onto the stack. |
| |
| #### PushFalse |
| |
| Push `false` onto the stack. |
| |
| #### PushInt X |
| |
| Push int X onto the stack. |
| |
| #### Drop1 |
| |
| Drop 1 value from the stack |
| |
| #### Push X |
| |
| Push Locals[X] to the stack. |
| |
| #### StoreLocal X; PopLocal X |
| |
| Store top of the stack into Locals[X] and pop it if needed. |
| |
| #### LoadFieldTOS D |
| |
| Push value of field ConstantPool[D] from object SP[0]. |
| |
| #### StoreFieldTOS D |
| |
| Store value SP[0] into field ConstantPool[D] of object SP[-1]. |
| |
| #### StoreIndexedTOS |
| |
| Store SP[0] into array SP[-2] at index SP[-1]. No type or index checking is done. |
| SP[-2] is assumed to be an array (created with CreateArrayTOS), SP[-1] is an int. |
| |
| #### PushStatic D |
| |
| Pushes value of the static field ConstantPool[D] on to the stack. |
| |
| #### StoreStaticTOS D |
| |
| Stores SP[0] into the static field ConstantPool[D]. |
| |
| #### Jump target |
| |
| Jump to the given target. Target is specified as offset from the PC of the |
| jump instruction. |
| |
| #### JumpIfNoAsserts target |
| |
| Jump to the given target if assertions are not enabled. |
| Target is specified as offset from the PC of the jump instruction. |
| |
| #### JumpIfNotZeroTypeArgs target |
| |
| Jump to the given target if number of passed function type |
| arguments is not zero. |
| Target is specified as offset from the PC of the jump instruction. |
| |
| #### JumpIfUnchecked target |
| |
| May jump to the given target if current function was called through unchecked |
| call instruction (UncheckedInterfaceCall, UncheckedClosureCall). |
| This instruction can be used to skip checking of type argument bounds and |
| types of generic-covariant parameters. |
| This is an optimization, so it is valid to treat this instruction as a no-op. |
| Target is specified as offset from the PC of the jump instruction. |
| |
| #### JumpIfEqStrict target; JumpIfNeStrict target |
| |
| Jump to the given target if SP[-1] is the same (JumpIfEqStrict) / |
| not the same (JumpIfNeStrict) object as SP[0]. |
| |
| #### JumpIfTrue target; JumpIfFalse target; JumpIfNull target; JumpIfNotNull target |
| |
| Jump to the given target if SP[0] is true/false/null/not null. |
| |
| #### Suspend target |
| |
| Create a snapshot of the current frame and store it in the suspend |
| state object. Execution can be resumed from the suspend state |
| at the given target PC. |
| Target is specified as offset from the PC of the suspend instruction. |
| The filled suspend state object is stored into the reserved suspend |
| state local variable. Current function frame should be created with |
| EntrySuspendable instruction. |
| |
| #### DirectCall D, F |
| |
| Invoke the target function with arguments SP[-(F-1)], ..., SP[0]. |
| ContantPool[D] is a ConstantDirectCall specifying target and |
| arguments descriptor. |
| |
| #### InterfaceCall D, F |
| |
| Lookup and invoke instance method with arguments SP[-(F-1)], ..., SP[0]. |
| ContantPool[D] is a ConstantInterfaceCall specifying interface target and |
| arguments descriptor. |
| Method has to be declared (explicitly or implicitly) in an interface |
| implemented by a receiver SP[0], and passed arguments are valid for the |
| interface method declaration. |
| |
| #### UncheckedInterfaceCall D, F |
| |
| Same as InterfaceCall, but can omit type checks of generic-covariant |
| parameters. |
| |
| #### InstantiatedInterfaceCall D, F |
| |
| Same as InterfaceCall, but provides additional information about instantiated static |
| type of a (generic) receiver via ConstantInstantiatedInterfaceCall. At run time, if |
| actual type of receiver matches static type, then type checks of generic-covariant |
| Parameters can be omitted. |
| |
| #### UncheckedClosureCall D, F |
| |
| Call closure SP[0] with arguments SP[-F], ..., SP[-1]. |
| ContantPool[D] is a ConstantObjectRef with ArgDesc object specifying arguments |
| descriptor. |
| Closure should have a statically known function type; parameters should have |
| correct types according to the closure’s function type, so parameter type checks |
| can be omitted. |
| |
| #### DynamicCall D, F |
| |
| Lookup and invoke instance method with arguments SP[-(F-1)], ..., SP[0]. |
| ContantPool[D] is a ConstantDynamicCall specifying selector and |
| arguments descriptor. |
| |
| #### ExternalCall D |
| |
| Invoke body of an external method. |
| ContantPool[D] is a ConstantExternalCall. |
| |
| #### ReturnTOS |
| |
| Return to the caller using SP[0] as a result. |
| |
| #### AssertAssignable A, E |
| |
| Assert that instance SP[-4] is assignable to variable named SP[0] of |
| type SP[-1] with instantiator type arguments SP[-3] and function type |
| arguments SP[-2] using ConstantSubtypeTestCache at ConstantPool[E]. |
| If A is 1, then the instance may be an int. |
| |
| Instance remains on stack. Other arguments are consumed. |
| |
| #### AssertSubtype |
| |
| Assert that one type is a subtype of another. Throws a TypeError |
| otherwise. The stack has the following arguments on it: |
| |
| ``` |
| SP[-4] instantiator type args |
| SP[-3] function type args |
| SP[-2] sub-type |
| SP[-1] super-type |
| SP[-0] destination name |
| ``` |
| |
| All 5 arguments are consumed from the stack and no results is pushed. |
| |
| #### LoadTypeArgumentsField D |
| |
| Load instantiator type arguments from an instance SP[0]. |
| ConstantPool[D] should be a ConstantTypeArgumentsField corresponding |
| to an instance's generic (base) class. |
| |
| #### InstantiateType D |
| |
| Instantiate type ConstantPool[D] with instantiator type arguments SP[-1] and |
| function type arguments SP[0]. |
| |
| #### InstantiateTypeArgumentsTOS A, E |
| |
| Instantiate type arguments ConstantPool[E] with instantiator type arguments SP[-1] |
| and function type arguments SP[0]. A != 0 indicates that resulting type |
| arguments are all dynamic if both instantiator and function type arguments are |
| all dynamic. |
| |
| #### Throw A |
| |
| Throw (Rethrow if A != 0) exception. Exception object and stack object |
| are taken from the top of the stack. |
| |
| #### MoveSpecial A, Y |
| |
| Copy value from special variable to Locals[Y]. Currently only |
| used to pass exception object (A = 0) and stack trace object (A = 1) to |
| catch handler. |
| |
| #### SetFrame A |
| |
| Reset expression stack to empty. |
| ‘A’ should be equal to the frame size allocated by prologue instructions. |
| Used to drop temporaries from the stack in the exception handler. |
| |
| #### BooleanNegateTOS |
| |
| SP[0] = !SP[0] |
| |
| #### EqualsNull |
| |
| SP[0] = (SP[0] == null) ? true : false |
| |
| #### NegateInt |
| |
| Equivalent to invocation of unary int operator-. |
| Receiver should have static type int. |
| |
| SP[0] = -SP[0] |
| |
| #### AddInt; SubInt; MulInt; TruncDivInt; ModInt; BitAndInt; BitOrInt; BitXorInt; ShlInt; ShrInt |
| |
| Equivalent to invocation of binary int operator +, -, *, ~/, %, &, |, |
| ^, << or >>. Receiver and argument should have static type int. |
| |
| SP[0] = SP[-1] <op> SP[0] |
| |
| #### CompareIntEq; CompareIntGt; CompareIntLt; CompareIntGe; CompareIntLe |
| |
| Equivalent to invocation of binary int operator ==, >, <, >= or <=. |
| Receiver and argument should have static type int. |
| |
| SP[0] = SP[-1] <op> SP[0] ? true : false |
| |
| #### NegateDouble |
| |
| Equivalent to invocation of unary double operator-. |
| Receiver should have static type double. |
| |
| SP[0] = -SP[0] |
| |
| #### AddDouble; SubDouble; MulDouble; DivDouble |
| |
| Equivalent to invocation of binary double operator +, -, *, / |
| Receiver and argument should have static type double. |
| |
| SP[0] = SP[-1] <op> SP[0] |
| |
| #### CompareDoubleEq; CompareDoubleGt; CompareDoubleLt; CompareDoubleGe; CompareDoubleLe |
| |
| Equivalent to invocation of binary double operator ==, >, <, >= or <=. |
| Receiver and argument should have static type double. |
| |
| SP[0] = SP[-1] <op> SP[0] ? true : false |
| |
| #### AllocateClosure D |
| |
| Allocate closure object for closure function ConstantPool[D]. |
| |
| #### DebugCheck |
| |
| No-op. Provides a point where debugger can stop executing code when single stepping |
| or when it hits a breakpoint. |
| |