blob: 6fc09429196e44a26b0e16d00d2285495c2fc831 [file] [log] [blame]
// 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 'dart:typed_data';
import 'package:kernel/ast.dart';
import 'package:kernel/binary/ast_from_binary.dart';
/// Helper class for continuously loading kernel dill files that consists of
/// sub components as when serialized via the incremental serializer.
///
/// For instance the frontend-server serializes with the incremental serializer
/// which works by outputting previously serialized component bytes, i.e. a
/// single dill actually contains several components, and many of these
/// components will byte-for-byte be the same between compiles.
///
/// This helper class will do checksums of the bytes of each sub component (and
/// byte-for-byte check on match), to avoid loading previously loaded data.
///
/// In some cases we might even have access to identical data between loads
/// which allows us to skip creating a checksum and comparing bytes, but instead
/// rely on the identity of the list.
class MultiBinaryLoader {
/// Map from the adler32 checksum to the (possibly multiple) previously loaded
/// data.
final Map<int, List<_LoadedData>> _adlerToData = {};
/// Identity-map from the original list to the loaded equivalent.
final Map<Uint8List, _LoadedData> _identityHashToData = new Map.identity();
/// The canonical name root *everything* is loaded in to.
final CanonicalName _canonicalNameRoot = new CanonicalName.root();
/// Check if the bytes match, i.e. if the bytes in [data] from [from] to [to]
/// matches the bytes in the [candidate].
bool _checkCandidate(
Uint8List data, int from, int to, _LoadedData candidate) {
int length = to - from;
if (length != candidate.data.length) return false;
for (int i = 0, j = from; i < length; i++, j++) {
if (data[j] != candidate.data[i]) {
return false;
}
}
return true;
}
/// Load the data provided in [allData] into a component.
///
/// [allData] is a list to allow loading several chunks, e.g. loading the
/// platform together with a serialized component without the platform.
/// [alternativeData] can optionally give an alternative version of some data
/// range. This would possibly allow for skipping the creation of a checksum.
Component load(List<Uint8List> allData,
Uint8List? Function(Uint8List data, int from, int to)? alternativeData) {
List<_LoadedData> subComponents = [];
for (Uint8List orgData in allData) {
for (SubComponentView subView in BinaryBuilder.index(orgData)) {
int from = subView.componentStartOffset;
int to = subView.componentStartOffset + subView.componentFileSize;
if (from == 0 && to == orgData.length) {
_LoadedData? found = _identityHashToData[orgData];
if (found != null) {
subComponents.add(found);
continue;
}
}
Uint8List data = orgData;
if (alternativeData != null) {
Uint8List? alternative = alternativeData(data, from, to);
if (alternative != null) {
data = alternative;
from = 0;
to = alternative.length;
_LoadedData? found = _identityHashToData[alternative];
if (found != null) {
subComponents.add(found);
continue;
}
}
}
_LoadedData? found;
final int checksum = _adler32(data, from, to);
final List<_LoadedData> lookup = _adlerToData[checksum] ??= [];
for (_LoadedData candidate in lookup) {
// Checksum matches --- check the actual bytes.
if (_checkCandidate(data, from, to, candidate)) {
found = candidate;
break;
}
}
if (found == null) {
// Load and add.
// The subData is a view, so if we save that we'll make the while file
// live in memory. We don't want that so we make a "physical" copy.
Uint8List subData = new Uint8List(to - from);
subData.setRange(0, subData.length, data, from);
BinaryBuilder bb = new BinaryBuilder(subData,
disableLazyReading: true,
useGrowableLists: false,
alwaysCreateNewNamedNodes: true);
Component component = new Component(nameRoot: _canonicalNameRoot);
bb.readComponent(component);
List<CanonicalName> linkTable = bb.linkTable;
found = new _LoadedData(subData, component, linkTable);
lookup.add(found);
}
if (from == 0 && to == data.length) {
_identityHashToData[data] = found;
}
// Add data to combined output.
subComponents.add(found);
}
}
// Create the combined component and link it up.
Component result = new Component(nameRoot: _canonicalNameRoot);
for (_LoadedData subComponent in subComponents) {
// At first none of the canonical names (or, references really) in the
// requested data should point to anything.
for (CanonicalName canonicalName in subComponent.linkTable) {
canonicalName.referenceOrNull?.node = null;
}
result.libraries.addAll(subComponent.component.libraries);
// TODO: Set main method, combine sources etc.
}
// Then we relink everything, effectively making canonical names
// (references) point to the right things iff it's in the data we was asked
// to load.
for (Library library in result.libraries) {
library.parent = result;
library.relink();
}
// We could check all the used canonical names if they're set.
// TODO: Should this be optional?
for (_LoadedData subComponent in subComponents) {
for (CanonicalName canonicalName in subComponent.linkTable) {
canonicalName.checkThisCanonicalName();
}
}
return result;
}
}
class _LoadedData {
final Uint8List data;
final Component component;
final List<CanonicalName> linkTable;
_LoadedData(this.data, this.component, this.linkTable);
}
/// Calculate the "Adler-32" checksum.
///
/// See https://en.wikipedia.org/wiki/Adler-32 for details.
int _adler32(Uint8List data, int from, int to) {
int a = 1;
int b = 0;
for (int i = from; i < to; i++) {
a += data[i];
b += a;
if (i & 255 == 255) {
a %= 65521;
b %= 65521;
}
}
a %= 65521;
b %= 65521;
return (b << 16) | a;
}