blob: b932926b8d235b8b12e3f1634440d02b592195f0 [file] [edit]
// Copyright (c) 2026, 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 'package:cfg/ir/global_context.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/ir/source_position.dart';
import 'package:cfg/utils/bit_vector.dart';
import 'package:kernel/ast.dart' as ast;
import 'package:native_compiler/back_end/safepoint.dart';
class ExceptionHandler._(
final int index,
final int outerIndex,
final bool isSynthetic,
final bool needsStackTrace,
final List<ast.DartType> guardTypes,
) {
late final int pcOffset;
bool get hasCatchAll => guardTypes.any(_isCatchAll);
static bool _isCatchAll(ast.DartType guardType) => switch (guardType) {
ast.DynamicType() => true,
ast.VoidType() => true,
ast.InterfaceType() =>
guardType.classNode == GlobalContext.instance.coreTypes.objectClass,
ast.FutureOrType() => _isCatchAll(guardType.typeArgument),
ast.ExtensionType() => _isCatchAll(guardType.extensionTypeErasure),
_ => false,
};
}
/// Metadata describing exception handlers.
class ExceptionHandlers {
/// Whether this code has a top-level async exception handler.
final bool hasAsyncHandler;
final Map<CatchBlock, ExceptionHandler> _handlers = {};
ExceptionHandlers({required this.hasAsyncHandler});
Iterable<ExceptionHandler> get handlers => _handlers.values;
ExceptionHandler getHandler(CatchBlock catchBlock) =>
_handlers[catchBlock] ??= _createHandler(catchBlock);
ExceptionHandler _createHandler(CatchBlock catchBlock) {
final outerHandler = catchBlock.exceptionHandler;
final outerIndex = outerHandler != null
? getHandler(outerHandler).index
: -1;
var needsStackTrace = false;
for (final instr in catchBlock) {
if (instr is! Parameter) break;
if (instr.variable.isStackTraceVariable) {
needsStackTrace = instr.hasUses;
break;
}
}
return ExceptionHandler._(
_handlers.length,
outerIndex,
catchBlock.isSynthetic,
needsStackTrace,
catchBlock.guardTypes,
);
}
@override
int get hashCode =>
_handlers.isEmpty ? hasAsyncHandler.hashCode : identityHashCode(this);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(_handlers.isEmpty &&
other is ExceptionHandlers &&
other._handlers.isEmpty &&
this.hasAsyncHandler == other.hasAsyncHandler);
}
class CallSite(
final int pcOffset,
final int exceptionHandlerIndex,
final SourcePosition sourcePosition,
);
/// Metadata describing call sites (aka PC descriptors).
class PcDescriptors {
final List<CallSite> callSites = [];
void add(CallSite cs) {
assert(callSites.isEmpty || callSites.last.pcOffset < cs.pcOffset);
callSites.add(cs);
}
@override
int get hashCode => callSites.isEmpty ? 37 : identityHashCode(this);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(callSites.isEmpty && other is PcDescriptors && other.callSites.isEmpty);
}
class ExceptionSite(final int pcOffset) {
// TODO: add moves
}
/// Metadata describing how to move values from an exception site to the exception handler.
class CatchEntryMoves {
final List<ExceptionSite> exceptionSites = [];
void add(ExceptionSite es) {
exceptionSites.add(es);
}
@override
int get hashCode => exceptionSites.isEmpty ? 31 : identityHashCode(this);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(exceptionSites.isEmpty &&
other is CatchEntryMoves &&
other.exceptionSites.isEmpty);
}
class CodeSourcePosition(
final int pcOffset,
final SourcePosition sourcePosition,
);
/// Metadata describing source positions.
class CodeSourceMap {
final List<CodeSourcePosition> sourcePositions = [];
void add(CodeSourcePosition sp) {
if (sourcePositions.isNotEmpty) {
assert(sourcePositions.last.pcOffset < sp.pcOffset);
if (sourcePositions.last.sourcePosition == sp.sourcePosition) {
return;
}
}
sourcePositions.add(sp);
}
@override
int get hashCode => sourcePositions.isEmpty ? 23 : identityHashCode(this);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(sourcePositions.isEmpty &&
other is CodeSourceMap &&
other.sourcePositions.isEmpty);
}
class StackMap(
final int pcOffset,
// Number of slots at the beginning of the frame, described by this stack map.
final int prefixBits,
// Number of slots at the end of the frame, described by this stack map.
final int suffixBits,
// Bit vector of [prefixBits] + [suffixBits] length.
// Each set bit marks stack slot containing an object pointer.
// Slots are numbered in the direction of stack growth (down).
// Prefix bits correspond to the slots immediately following fixed frame.
// Suffix bits correspond to the slots at the end of the frame, up to the callee frame.
// All stack slots between prefix and suffix contain object pointers.
//
// FP -> [fixed frame]
// [slot for prefix bit 0]
// ...
// [slot for prefix bit (prefixBits-1)]
// [object pointer]
// ...
// [object pointer]
// [slot for suffix bit 0]
// ...
// [slot for suffix bit (suffixBits-1)]
// [callee frame]
final BitVector bits,
);
/// Metadata describing stack maps at safepoints.
class CompressedStackMaps {
final List<StackMap> stackMaps = [];
void add(int pcOffset, Safepoint safepoint, int frameSize) {
assert(stackMaps.isEmpty || stackMaps.last.pcOffset < pcOffset);
BitVector stackMap = safepoint.stackMap;
if (stackMap.capacity < frameSize) {
stackMap = stackMap.expand(frameSize);
}
// Find maximum span of 1 bits in the stack map to skip.
var gapPos = 0;
var gapLen = 0;
for (var i = 0; i < frameSize;) {
if (stackMap[i]) {
var len = 1;
while (i + len < frameSize && stackMap[i + len]) {
++len;
}
if (len > gapLen) {
gapPos = i;
gapLen = len;
}
i += len;
} else {
++i;
}
}
final prefixBits = gapPos;
final suffixBits = (frameSize - prefixBits - gapLen);
final bits = BitVector(prefixBits + suffixBits);
bits.setRange(0, prefixBits, stackMap, 0);
bits.setRange(
prefixBits,
prefixBits + suffixBits,
stackMap,
prefixBits + gapLen,
);
stackMaps.add(StackMap(pcOffset, prefixBits, suffixBits, bits));
}
@override
int get hashCode => stackMaps.isEmpty ? 37 : identityHashCode(this);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(stackMaps.isEmpty &&
other is CompressedStackMaps &&
other.stackMaps.isEmpty);
}