| # 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 = 3; | |
| // 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; | |
| RecordedCoverageArray[] recordedCoverage; | |
| 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, isExtensionTypeMember, isShared); | |
| 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, isExtensionTypeMember, isInvisible); | |
| 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, | |
| hasRecordedCoverage) | |
| 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 hasRecordedCoverage | |
| // Offset of RecordedCoverageArray in ‘recordedCoverage’ section of | |
| // BytecodeFile. | |
| Uint recordedCoverageOffset; | |
| 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, hasAnnotations, hasPragma, isInvisible) | |
| // 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; | |
| if hasAnnotations | |
| // Offset of closure annotations in ‘annotations’ section of | |
| // component, followed by parameter annotations. | |
| UInt annotationsOffset; | |
| } | |
| type ClosureCode { | |
| UInt flags = (hasExceptionsTable, hasSourcePositions, hasLocalVariables, | |
| capturesOnlyFinalNotLateVars, hasLocalFunctionId, | |
| hasRecordedCoverage) | |
| if hasLocalFunctionId | |
| UInt localFunctionId; | |
| 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 hasRecordedCoverage | |
| // Offset of RecordedCoverageArray in ‘recordedCoverage’ section of | |
| // BytecodeFile. | |
| UInt recordedCoverageOffset; | |
| } | |
| ``` | |
| ### 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; | |
| } | |
| type ConstantNativeFunction extends ConstantPoolEntry { | |
| Byte tag = 16; | |
| } | |
| type ConstantDeferredLibraryPrefix extends ConstantPoolEntry { | |
| Byte tag = 17; | |
| PackedObject name; | |
| PackedObject enclosingLibrary; | |
| PackedObject targetLibrary; | |
| } | |
| // Occupies 2 entries in the constant pool | |
| type ConstantAllocateClosure extends ConstantPoolEntry { | |
| Byte tag = 18; | |
| UInt closureIndex; | |
| UInt numElements; | |
| UInt flags = (hasDelayedTypeArguments, hasInstantiatorTypeArguments, hasFunctionTypeArguments); | |
| } | |
| ``` | |
| ### 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, encodedFileOffset), ordered by PC | |
| // (offset of bytecode instruction). | |
| // | |
| // An encodedFileOffset encodes a fileOffset and a set of flags. | |
| // | |
| // If the encodedFileOffset is non-negative, then the encodedFileOffset | |
| // is the same numeric value as the fileOffset. | |
| // | |
| // If the encodedFileOffset is -1, then there is no source position | |
| // associated with the given PC. | |
| // | |
| // Otherwise, encodedFileOffset = -((fileOffset << n) | flags) -1, | |
| // where n is the number of flags below: | |
| // - synthetic (bit 0): set if the source position corresponds to | |
| // synthetic code (e.g., a source position that should not be used | |
| // for debugger breakpoints or pausing or for code coverage). | |
| // - yieldPoint (bit 1): set if the source position corresponds to | |
| // bytecode that implements a yield point. | |
| // | |
| // For two consecutive entries (PC1, FileOffset1), (PC2, FileOffset2) all | |
| // bytecode instructions in range [PC1,PC2) correspond to an encoded 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, hasConstConstructorCoverageFlag) | |
| PackedObject importUri; | |
| if hasLineStarts | |
| // Offset of line starts in ‘lineStarts’ section of BytecodeFile. | |
| UInt lineStartsOffset; | |
| if hasSource | |
| PackedString source; | |
| if hasConstConstructorCoverageFlag | |
| List<PackedObject> coveredConstConstructors; | |
| } | |
| 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; | |
| } | |
| ``` | |
| ### Recorded coverage | |
| ``` | |
| type RecordedCoverageArray { | |
| UInt numEntries; | |
| // Unordered encoded list of entries. | |
| RecordedCoverageEntry[numEntries] entries; | |
| } | |
| type RecordedCoverageEntry = { | |
| // The index of the entry's type in the RecordedEntryType enum. | |
| UInt type; | |
| // Delta-encoded file offset. | |
| SLEB128 fileOffset; | |
| } | |
| enum RecordedCoverageType = { | |
| regular = 0, | |
| branchTarget = 1; | |
| } | |
| ``` | |
| ## 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. | |
| #### Dup | |
| Duplicates the top of the stack. | |
| #### 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. | |
| #### FfiCall D | |
| Invoke native target of FFI call. | |
| ContantPool[D] is a ConstantNativeFunction that is used to cache | |
| the resolved function's entry point. | |
| #### ResolveNativeFunction D | |
| Resolve the native function for the constant Native instance at SP[0]. | |
| ContantPool[D] is a ConstantNativeFunction that is used to cache | |
| the resolved function's entry point. | |
| #### 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 described by ConstantAllocateClosure in ConstantPool[D]. | |
| #### LoadClosureElement D | |
| Load element [D] from closure SP[0] and push it onto the stack. | |
| #### StoreClosureElement D | |
| Store object SP[0] into the element [D] of closure SP[-1]. | |
| #### Nop | |
| No-op. Provides a unique PC offset to ensure an emitted source position is not | |
| overwritten by a different source position emitted by the next instruction. | |
| #### RecordCoverage A, E | |
| Records coverage. [A] is the index in the RecordedCoverageType enum for the type | |
| of coverage being recorded, and [E] is the index of the entry in | |
| the RecordedCoverageArray. | |
| The information in [A] is redundant, but allows the interpreter to check to see | |
| if the isolate group is currently recorded that type of coverage without needing | |
| either to iterate over the serialized RecordedCoverageArray or to index into the | |
| bytecode's coverage array, which may be lazily allocated. |