[vm,modular_aot] Boxing/unboxing of int and double values

TEST=ci
Issue: https://github.com/dart-lang/sdk/issues/61635
Change-Id: I9fe47364bf772ccedf21f95aa5b16b92b0215b3e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/486240
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Alexander Markov
2026-03-09 11:34:09 -07:00
committed by Commit Queue
parent f02b704216
commit d97566551b
25 changed files with 1057 additions and 103 deletions
+94 -7
View File
@@ -27,20 +27,31 @@ extension type ConstantValue(ast.Constant constant) {
factory ConstantValue.fromString(String value) =>
ConstantValue(ast.StringConstant(value));
int get intValue => (constant as ast.IntConstant).value;
double get doubleValue => (constant as ast.DoubleConstant).value;
int get intValue => switch (constant) {
ast.IntConstant(:var value) => value,
UnboxedIntConstant(:var value) => value,
_ => throw 'Unexpected int constant ${constant.runtimeType}',
};
double get doubleValue => switch (constant) {
ast.DoubleConstant(:var value) => value,
UnboxedDoubleConstant(:var value) => value,
_ => throw 'Unexpected double constant ${constant.runtimeType}',
};
bool get boolValue => (constant as ast.BoolConstant).value;
String get stringValue => (constant as ast.StringConstant).value;
bool get isInt => constant is ast.IntConstant;
bool get isDouble => constant is ast.DoubleConstant;
bool get isInt =>
constant is ast.IntConstant || constant is UnboxedIntConstant;
bool get isDouble =>
constant is ast.DoubleConstant || constant is UnboxedDoubleConstant;
bool get isBool => constant is ast.BoolConstant;
bool get isNull => constant is ast.NullConstant;
bool get isString => constant is ast.StringConstant;
bool get isUnboxed => constant is UnboxedConstant;
CType get type => switch (constant) {
ast.IntConstant() => const IntType(),
ast.DoubleConstant() => const DoubleType(),
ast.IntConstant() || UnboxedIntConstant() => const IntType(),
ast.DoubleConstant() || UnboxedDoubleConstant() => const DoubleType(),
ast.BoolConstant() => const BoolType(),
ast.NullConstant() => const NullType(),
ast.StringConstant() => const StringType(),
@@ -52,13 +63,17 @@ extension type ConstantValue(ast.Constant constant) {
bool get isZero => switch (constant) {
ast.IntConstant(:var value) => value == 0,
UnboxedIntConstant(:var value) => value == 0,
ast.DoubleConstant(:var value) => value == 0.0,
UnboxedDoubleConstant(:var value) => value == 0.0,
_ => false,
};
bool get isNegative => switch (constant) {
ast.IntConstant(:var value) => value < 0,
UnboxedIntConstant(:var value) => value < 0,
ast.DoubleConstant(:var value) => value.isNegative,
UnboxedDoubleConstant(:var value) => value.isNegative,
_ => false,
};
@@ -296,7 +311,9 @@ class SentinelConstant extends ast.AuxiliaryConstant {
void visitChildren(ast.Visitor v) {}
@override
void toTextInternal(ast_printer.AstPrinter printer) => '#sentinel';
void toTextInternal(ast_printer.AstPrinter printer) {
printer.write('#sentinel');
}
@override
String toString() => toStringInternal();
@@ -310,3 +327,73 @@ class SentinelConstant extends ast.AuxiliaryConstant {
@override
ast.DartType getType(StaticTypeContext context) => const ast.DynamicType();
}
/// Base class for unboxed constant values.
///
/// Used by certain back-ends to distinguish raw unboxed values
/// (incompatible with Dart objects) from regular constants.
abstract base class UnboxedConstant extends ast.AuxiliaryConstant {
UnboxedConstant();
}
/// Unboxed int constant.
///
/// Used by certain back-ends to distinguish raw unboxed values
/// (incompatible with Dart objects) from regular constants.
final class UnboxedIntConstant extends UnboxedConstant {
final int value;
UnboxedIntConstant(this.value);
@override
void visitChildren(ast.Visitor v) {}
@override
void toTextInternal(ast_printer.AstPrinter printer) {
printer.write('#unboxed $value');
}
@override
String toString() => 'UnboxedIntConstant($value)';
@override
int get hashCode => value.hashCode;
@override
bool operator ==(Object other) =>
other is UnboxedIntConstant && other.value == value;
@override
ast.DartType getType(StaticTypeContext context) =>
context.typeEnvironment.coreTypes.intNonNullableRawType;
}
/// Unboxed double constant.
///
/// Used by certain back-ends to distinguish raw unboxed values
/// (incompatible with Dart objects) from regular constants.
final class UnboxedDoubleConstant extends UnboxedConstant {
final double value;
UnboxedDoubleConstant(this.value);
@override
void visitChildren(ast.Visitor v) {}
@override
void toTextInternal(ast_printer.AstPrinter printer) {
printer.write('#unboxed $value');
}
@override
String toString() => 'UnboxedDoubleConstant($value)';
@override
int get hashCode => value.hashCode;
@override
bool operator ==(Object other) =>
other is UnboxedDoubleConstant && identical(value, other.value);
@override
ast.DartType getType(StaticTypeContext context) =>
context.typeEnvironment.coreTypes.doubleNonNullableRawType;
}
+20
View File
@@ -433,6 +433,26 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor<void> {
@override
void visitSetListElement(SetListElement instr) {}
@override
void visitBoxInt(BoxInt instr) {
assert(instr.operand.type is IntType);
}
@override
void visitBoxDouble(BoxDouble instr) {
assert(instr.operand.type is DoubleType);
}
@override
void visitUnboxInt(UnboxInt instr) {
assert(instr.operand.type is IntType);
}
@override
void visitUnboxDouble(UnboxDouble instr) {
assert(instr.operand.type is DoubleType);
}
@override
void visitParallelMove(ParallelMove instr) {}
}
+66
View File
@@ -1647,6 +1647,72 @@ final class SetListElement extends Instruction
R accept<R>(InstructionVisitor<R> v) => v.visitSetListElement(this);
}
/// Base class for boxing instructions.
abstract base class Box extends Definition
with CanThrow, Pure, BackendInstruction {
Box(super.graph, super.sourcePosition, Definition operand)
: super(inputCount: 1) {
setInputAt(0, operand);
}
Definition get operand => inputDefAt(0);
}
/// Create a box out of raw int value.
final class BoxInt extends Box {
BoxInt(super.graph, super.sourcePosition, super.operand);
@override
CType get type => const IntType();
@override
R accept<R>(InstructionVisitor<R> v) => v.visitBoxInt(this);
}
/// Create a box out of raw double value.
final class BoxDouble extends Box {
BoxDouble(super.graph, super.sourcePosition, super.operand);
@override
CType get type => const DoubleType();
@override
R accept<R>(InstructionVisitor<R> v) => v.visitBoxDouble(this);
}
/// Base class for unboxing instructions.
abstract base class Unbox extends Definition
with NoThrow, Pure, BackendInstruction {
Unbox(super.graph, super.sourcePosition, Definition operand)
: super(inputCount: 1) {
setInputAt(0, operand);
}
Definition get operand => inputDefAt(0);
}
/// Get raw int value out of the box.
final class UnboxInt extends Unbox {
UnboxInt(super.graph, super.sourcePosition, super.operand);
@override
CType get type => const IntType();
@override
R accept<R>(InstructionVisitor<R> v) => v.visitUnboxInt(this);
}
/// Get raw double value out of the box.
final class UnboxDouble extends Unbox {
UnboxDouble(super.graph, super.sourcePosition, super.operand);
@override
CType get type => const DoubleType();
@override
R accept<R>(InstructionVisitor<R> v) => v.visitUnboxDouble(this);
}
/// Base class for move operations, part of [ParallelMove].
abstract base class MoveOp {}
+8
View File
@@ -51,6 +51,10 @@ abstract interface class InstructionVisitor<R> {
R visitCompareAndBranch(CompareAndBranch instr);
R visitAllocateList(AllocateList instr);
R visitSetListElement(SetListElement instr);
R visitBoxInt(BoxInt instr);
R visitBoxDouble(BoxDouble instr);
R visitUnboxInt(UnboxInt instr);
R visitUnboxDouble(UnboxDouble instr);
R visitParallelMove(ParallelMove instr);
}
@@ -120,6 +124,10 @@ abstract mixin class DefaultInstructionVisitor<R>
R visitAllocateList(AllocateList instr) => defaultBackendInstruction(instr);
R visitSetListElement(SetListElement instr) =>
defaultBackendInstruction(instr);
R visitBoxInt(BoxInt instr) => defaultBackendInstruction(instr);
R visitBoxDouble(BoxDouble instr) => defaultBackendInstruction(instr);
R visitUnboxInt(UnboxInt instr) => defaultBackendInstruction(instr);
R visitUnboxDouble(UnboxDouble instr) => defaultBackendInstruction(instr);
R visitParallelMove(ParallelMove instr) => defaultBackendInstruction(instr);
}
@@ -550,6 +550,26 @@ final class ConstantPropagation extends Pass
@override
void visitSetListElement(SetListElement instr) {}
@override
void visitBoxInt(BoxInt instr) {
_setNonConstant(instr);
}
@override
void visitBoxDouble(BoxDouble instr) {
_setNonConstant(instr);
}
@override
void visitUnboxInt(UnboxInt instr) {
_setNonConstant(instr);
}
@override
void visitUnboxDouble(UnboxDouble instr) {
_setNonConstant(instr);
}
@override
void visitParallelMove(ParallelMove instr) {}
+12
View File
@@ -279,6 +279,18 @@ final class Simplification extends Pass
@override
Instruction visitSetListElement(SetListElement instr) => instr;
@override
Instruction visitBoxInt(BoxInt instr) => instr;
@override
Instruction visitBoxDouble(BoxDouble instr) => instr;
@override
Instruction visitUnboxInt(UnboxInt instr) => instr;
@override
Instruction visitUnboxDouble(UnboxDouble instr) => instr;
@override
Instruction visitParallelMove(ParallelMove instr) => instr;
@@ -7,6 +7,7 @@ import 'package:native_compiler/back_end/assembler.dart';
import 'package:native_compiler/back_end/code.dart';
import 'package:native_compiler/back_end/locations.dart';
import 'package:native_compiler/back_end/object_pool.dart';
import 'package:native_compiler/runtime/object_layout.dart';
import 'package:native_compiler/runtime/vm_defs.dart';
import 'package:cfg/ir/constant_value.dart';
@@ -322,7 +323,9 @@ const int B31 = (1 << 31);
/// TODO: support long branches, large offsets and floating-point instructions.
/// TODO: measure performance overhead of always checking encoding constraints.
final class Arm64Assembler extends Assembler with Uint32OutputBuffer {
Arm64Assembler(super.vmOffsets);
final ObjectLayout objectLayout;
Arm64Assembler(super.vmOffsets, this.objectLayout);
/// Create a [base + offset] address for arbitrary offset,
/// generating extra code if necessary.
@@ -503,7 +506,13 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer {
assert(reg != SP);
if (value.isInt) {
loadImmediate(reg, value.intValue);
if (value.isUnboxed) {
loadImmediate(reg, value.intValue);
} else if (objectLayout.isSmi(value.intValue)) {
loadImmediate(reg, value.intValue << smiShift);
} else {
loadFromPool(reg, value as Object);
}
} else {
loadFromPool(reg, value as Object);
}
@@ -697,8 +706,9 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer {
Register scratch1Reg,
Register scratch2Reg,
int instanceSize,
Label slowPath,
) {
Label slowPath, {
required bool initializeFields,
}) {
final endReg = scratch1Reg;
final newTopReg = scratch2Reg;
// Load Thread.top_ and Thread.end_.
@@ -712,34 +722,44 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer {
str(tagsReg, address(resultReg, vmOffsets.Object_tags_offset));
// TODO: figure out if we need store-store barrier here.
// TODO: support compressed pointers.
const maxUnrolledSize = 16 * wordSize;
if (instanceSize <= maxUnrolledSize) {
int offset = vmOffsets.Instance_first_field_offset;
for (; offset + 2 * wordSize <= instanceSize; offset += 2 * wordSize) {
stp(nullReg, nullReg, pairAddress(resultReg, offset));
}
if (offset < instanceSize) {
str(nullReg, address(resultReg, offset));
offset += wordSize;
}
assert(offset == instanceSize);
} else {
final fieldReg = scratch1Reg;
addImmediate(fieldReg, resultReg, vmOffsets.Instance_first_field_offset);
if (initializeFields) {
// TODO: support compressed pointers.
const maxUnrolledSize = 16 * wordSize;
if (instanceSize <= maxUnrolledSize) {
int offset = vmOffsets.Instance_first_field_offset;
for (; offset + 2 * wordSize <= instanceSize; offset += 2 * wordSize) {
stp(nullReg, nullReg, pairAddress(resultReg, offset));
}
if (offset < instanceSize) {
str(nullReg, address(resultReg, offset));
offset += wordSize;
}
assert(offset == instanceSize);
} else {
final fieldReg = scratch1Reg;
addImmediate(
fieldReg,
resultReg,
vmOffsets.Instance_first_field_offset,
);
final loop = Label();
bind(loop);
stp(
nullReg,
nullReg,
WritebackRegOffsetAddress(fieldReg, 2 * wordSize, isPostIndexed: true),
);
// There is at least two word (kAllocationRedZoneSize) gap at the end of page
// which makes it possible to initialize objects by two words at once and
// write slightly beyond the end.
cmp(fieldReg, newTopReg);
b(loop, Condition.unsignedLess);
final loop = Label();
bind(loop);
stp(
nullReg,
nullReg,
WritebackRegOffsetAddress(
fieldReg,
2 * wordSize,
isPostIndexed: true,
),
);
// There is at least two word (kAllocationRedZoneSize) gap at the end of page
// which makes it possible to initialize objects by two words at once and
// write slightly beyond the end.
cmp(fieldReg, newTopReg);
b(loop, Condition.unsignedLess);
}
}
addImmediate(resultReg, resultReg, heapObjectTag);
@@ -1055,6 +1075,19 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer {
ubfm(rd, rn, 0, 15, sz);
}
void asr(
Register rd,
Register rn,
int shift, [
OperandSize sz = OperandSize.s64,
]) {
if (shift == 0) {
mov(rd, rn, sz);
} else {
sbfm(rd, rn, shift, sz.bitWidth - 1, sz);
}
}
void _emitBitfieldMove(
int opcode,
Register rd,
@@ -1300,6 +1333,7 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer {
}
void _emitLoadStore(int opcode, Register rt, Address a, OperandSize sz) {
assert(!sz.is128);
switch (a) {
case RegOffsetAddress():
emit(
@@ -1323,6 +1357,37 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer {
}
}
void fldr(FPRegister rt, Address a, [OperandSize sz = OperandSize.s64]) {
_emitFPLoadStore(B22 | B26 | B27 | B28 | B29, rt, a, sz);
}
void fstr(FPRegister rt, Address a, [OperandSize sz = OperandSize.s64]) {
_emitFPLoadStore(B26 | B27 | B28 | B29, rt, a, sz);
}
void _emitFPLoadStore(int opcode, FPRegister rt, Address a, OperandSize sz) {
switch (a) {
case RegOffsetAddress():
emit(
opcode |
rt.encodingRt |
a.encoding(sz) |
(sz.is128 ? B23 : 0) |
((sz.log2sizeInBytes & 3) << 30),
);
case WritebackRegOffsetAddress():
emit(
opcode |
rt.encodingRt |
a.encoding(sz) |
(sz.is128 ? B23 : 0) |
((sz.log2sizeInBytes & 3) << 30),
);
default:
throw 'Unexpect address ${a.runtimeType}';
}
}
void ldp(
Register low,
Register high,
@@ -1530,6 +1595,10 @@ extension on Register {
int encodingRt2({bool allowSP = false}) => encoding(allowSP: allowSP) << 10;
}
extension on FPRegister {
int get encodingRt => index;
}
extension on Immediate {
int get encodingImm12 {
if (_isUint(12, value)) {
@@ -29,7 +29,8 @@ final class Arm64CodeGenerator extends CodeGenerator {
Arm64CodeGenerator(super.backEndState, this.functionRegistry);
@override
Assembler createAssembler() => _asm = Arm64Assembler(backEndState.vmOffsets);
Assembler createAssembler() =>
_asm = Arm64Assembler(backEndState.vmOffsets, backEndState.objectLayout);
@override
void enterFrame() {
@@ -856,6 +857,7 @@ final class Arm64CodeGenerator extends CodeGenerator {
AllocationStub.scratch2Reg,
instanceSize,
slowPath,
initializeFields: true,
);
if (typeArgsField != null) {
@@ -914,6 +916,7 @@ final class Arm64CodeGenerator extends CodeGenerator {
AllocationStub.scratch2Reg,
instanceSize,
slowPath,
initializeFields: true,
);
_asm.bind(initializeObject);
@@ -937,6 +940,115 @@ final class Arm64CodeGenerator extends CodeGenerator {
_asm.unimplemented('Unimplemented: code generation for SetListElement');
}
@override
void visitBoxInt(BoxInt instr) {
final operandReg = inputReg(instr, 0);
final tagsReg = temporaryReg(instr, 0);
final scratch1Reg = temporaryReg(instr, 1);
final scratch2Reg = temporaryReg(instr, 2);
final resultReg = outputReg(instr);
final done = Label();
final cls = GlobalContext.instance.coreTypes.index.getClass(
'dart:core',
'_Mint',
);
final instanceSize = vmOffsets.Mint_InstanceSize;
Label slowPath = addSlowPath(() {
_asm.unimplemented('Unimplemented: code generation for BoxInt slow path');
_asm.b(done);
});
_asm.adds(resultReg, operandReg, operandReg);
_asm.b(done, .noOverflow);
// TODO: compute tags at compile time.
_asm.loadFromPool(tagsReg, NewObjectTags(cls));
_asm.inlineAllocation(
resultReg,
tagsReg,
scratch1Reg,
scratch2Reg,
instanceSize,
slowPath,
initializeFields: false,
);
_asm.str(
operandReg,
_asm.fieldAddress(resultReg, vmOffsets.Mint_value_offset),
);
_asm.bind(done);
}
@override
void visitBoxDouble(BoxDouble instr) {
final operandReg = inputFPReg(instr, 0);
final tagsReg = temporaryReg(instr, 0);
final scratch1Reg = temporaryReg(instr, 1);
final scratch2Reg = temporaryReg(instr, 2);
final resultReg = outputReg(instr);
final done = Label();
final cls = GlobalContext.instance.coreTypes.index.getClass(
'dart:core',
'_Double',
);
final instanceSize = vmOffsets.Double_InstanceSize;
Label slowPath = addSlowPath(() {
_asm.unimplemented(
'Unimplemented: code generation for BoxDouble slow path',
);
_asm.b(done);
});
// TODO: compute tags at compile time.
_asm.loadFromPool(tagsReg, NewObjectTags(cls));
_asm.inlineAllocation(
resultReg,
tagsReg,
scratch1Reg,
scratch2Reg,
instanceSize,
slowPath,
initializeFields: false,
);
_asm.fstr(
operandReg,
_asm.fieldAddress(resultReg, vmOffsets.Double_value_offset),
);
_asm.bind(done);
}
@override
void visitUnboxInt(UnboxInt instr) {
var operandReg = inputReg(instr, 0);
final resultReg = outputReg(instr);
final done = Label();
if (operandReg == resultReg) {
_asm.mov(tempReg, operandReg);
operandReg = tempReg;
}
_asm.asr(resultReg, operandReg, smiShift);
_asm.tbz(operandReg, smiBit, done);
_asm.ldr(
resultReg,
_asm.fieldAddress(operandReg, vmOffsets.Mint_value_offset),
);
_asm.bind(done);
}
@override
void visitUnboxDouble(UnboxDouble instr) {
final operandReg = inputReg(instr, 0);
final resultReg = outputFPReg(instr);
_asm.fldr(
resultReg,
_asm.fieldAddress(operandReg, vmOffsets.Double_value_offset),
);
}
@override
void visitBinaryIntOp(BinaryIntOp instr) {
_asm.unimplemented('Unimplemented: code generation for BinaryIntOp');
@@ -270,6 +270,30 @@ final class Arm64Constraints extends Constraints {
anyCpuRegister,
]);
@override
InstructionConstraints? visitBoxInt(BoxInt instr) =>
const InstructionConstraints(
anyCpuRegister,
[anyCpuRegister],
[anyCpuRegister, anyCpuRegister, anyCpuRegister],
);
@override
InstructionConstraints? visitBoxDouble(BoxDouble instr) =>
const InstructionConstraints(
anyCpuRegister,
[anyFpuRegister],
[anyCpuRegister, anyCpuRegister, anyCpuRegister],
);
@override
InstructionConstraints? visitUnboxInt(UnboxInt instr) =>
const InstructionConstraints(anyCpuRegister, [anyCpuRegister]);
@override
InstructionConstraints? visitUnboxDouble(UnboxDouble instr) =>
const InstructionConstraints(anyFpuRegister, [anyCpuRegister]);
@override
InstructionConstraints? visitBinaryIntOp(BinaryIntOp instr) =>
InstructionConstraints(anyCpuRegister, [
@@ -8,14 +8,15 @@ import 'package:native_compiler/back_end/arm64/assembler.dart';
import 'package:native_compiler/back_end/assembler.dart';
import 'package:native_compiler/back_end/locations.dart';
import 'package:native_compiler/back_end/stub_code_generator.dart';
import 'package:native_compiler/runtime/object_layout.dart';
import 'package:native_compiler/runtime/type_utils.dart';
import 'package:native_compiler/runtime/vm_defs.dart';
abstract base class Arm64StubCodeGenerator implements StubCodeGenerator {
final Arm64Assembler _asm;
Arm64StubCodeGenerator(VMOffsets vmOffsets)
: _asm = Arm64Assembler(vmOffsets);
Arm64StubCodeGenerator(VMOffsets vmOffsets, ObjectLayout objectLayout)
: _asm = Arm64Assembler(vmOffsets, objectLayout);
void _generate();
@@ -44,7 +45,7 @@ final class AllocationStub extends Arm64StubCodeGenerator {
final ast.Class cls;
AllocationStub(super.vmOffsets, this.cls);
AllocationStub(super.vmOffsets, super.objectLayout, this.cls);
@override
void _generate() {
@@ -89,7 +90,12 @@ final class WriteBarrierStub extends Arm64StubCodeGenerator {
final Register objectReg;
final Register valueReg;
WriteBarrierStub(super.vmOffsets, this.objectReg, this.valueReg);
WriteBarrierStub(
super.vmOffsets,
super.objectLayout,
this.objectReg,
this.valueReg,
);
@override
void _generate() {
@@ -104,15 +110,20 @@ final class WriteBarrierStub extends Arm64StubCodeGenerator {
final class Arm64StubFactory extends StubFactory {
final VMOffsets vmOffsets;
Arm64StubFactory(this.vmOffsets, super.consumeGeneratedCode);
final ObjectLayout objectLayout;
Arm64StubFactory(
this.vmOffsets,
this.objectLayout,
super.consumeGeneratedCode,
);
@override
StubCodeGenerator allocationStubGenerator(ast.Class cls) =>
AllocationStub(vmOffsets, cls);
AllocationStub(vmOffsets, objectLayout, cls);
@override
StubCodeGenerator writeBarrierStubGenerator(
Register objectReg,
Register valueReg,
) => WriteBarrierStub(vmOffsets, objectReg, valueReg);
) => WriteBarrierStub(vmOffsets, objectLayout, objectReg, valueReg);
}
@@ -18,11 +18,13 @@ enum OperandSize {
s8,
s16,
s32,
s64;
s64,
simd128;
bool get is32 => (this == u32) || (this == s32);
bool get is64 => (this == u64) || (this == s64);
bool get is32or64 => is32 || is64;
bool get is128 => (this == simd128);
bool get isSigned =>
(this == s8) || (this == s16) || (this == s32) || (this == s64);
@@ -32,6 +34,7 @@ enum OperandSize {
u16 || s16 => 16,
u32 || s32 => 32,
u64 || s64 => 64,
simd128 => 128,
};
int get sizeInBytes => switch (this) {
@@ -39,6 +42,7 @@ enum OperandSize {
u16 || s16 => 2,
u32 || s32 => 4,
u64 || s64 => 8,
simd128 => 16,
};
int get log2sizeInBytes => switch (this) {
@@ -46,6 +50,7 @@ enum OperandSize {
u16 || s16 => 1,
u32 || s32 => 2,
u64 || s64 => 3,
simd128 => 4,
};
}
@@ -7,6 +7,7 @@ import 'package:native_compiler/back_end/code.dart';
import 'package:native_compiler/back_end/locations.dart';
import 'package:native_compiler/back_end/stack_frame.dart';
import 'package:native_compiler/back_end/stub_code_generator.dart';
import 'package:native_compiler/passes/unboxing.dart';
import 'package:native_compiler/runtime/object_layout.dart';
import 'package:native_compiler/runtime/vm_defs.dart';
@@ -22,6 +23,9 @@ class BackEndState {
/// Reusable stubs.
late final StubFactory stubFactory;
/// Boxed/unboxed representation.
late final Unboxing unboxing;
/// Block order for the code generation.
late final List<Block> codeGenBlockOrder;
@@ -78,7 +78,9 @@ final class LinearScanRegisterAllocator extends RegisterAllocator {
LinearScanRegisterAllocator(super.backEndState, this.constraints);
RegisterClass registerClass(Definition instr) =>
instr.type is DoubleType ? RegisterClass.fpu : RegisterClass.cpu;
instr.type is DoubleType && backEndState.unboxing.hasUnboxedResult(instr)
? RegisterClass.fpu
: RegisterClass.cpu;
int instructionPos(Instruction instr) => _instructionPos[instr.id];
int blockStartPos(Block block) => instructionPos(block);
+10 -1
View File
@@ -23,6 +23,7 @@ import 'package:native_compiler/back_end/stack_frame.dart';
import 'package:native_compiler/back_end/stub_code_generator.dart';
import 'package:native_compiler/passes/lowering.dart';
import 'package:native_compiler/passes/reorder_blocks.dart';
import 'package:native_compiler/passes/unboxing.dart';
import 'package:native_compiler/runtime/object_layout.dart';
import 'package:native_compiler/runtime/vm_defs.dart';
import 'package:native_compiler/snapshot/image_writer.dart';
@@ -85,7 +86,11 @@ abstract base class Configuration {
StubFactory createStubFactory(CodeConsumer consumeGeneratedCode) =>
switch (targetCPU) {
TargetCPU.arm64 => Arm64StubFactory(vmOffsets, consumeGeneratedCode),
TargetCPU.arm64 => Arm64StubFactory(
vmOffsets,
objectLayout,
consumeGeneratedCode,
),
};
ImageWriter createImageWriter() => switch (imageFormat) {
@@ -122,10 +127,12 @@ final class DevelopmentCompilerConfiguration extends Configuration {
StubFactory stubFactory,
CodeConsumer consumeGeneratedCode,
) {
final unboxing = Unboxing();
final backEndState = BackEndState();
backEndState.vmOffsets = vmOffsets;
backEndState.objectLayout = objectLayout;
backEndState.stubFactory = stubFactory;
backEndState.unboxing = unboxing;
backEndState.stackFrame = createStackFrame(function);
backEndState.consumeGeneratedCode = consumeGeneratedCode;
final constraints = createConstraints();
@@ -135,6 +142,8 @@ final class DevelopmentCompilerConfiguration extends Configuration {
ConstantPropagation(),
ControlFlowOptimizations(),
Lowering(functionRegistry, objectLayout),
unboxing,
ValueNumbering(simplification: Simplification()),
ReorderBlocks(backEndState),
LinearScanRegisterAllocator(backEndState, constraints),
RegisterAllocationChecker(backEndState, constraints),
@@ -18,8 +18,6 @@ import 'package:native_compiler/runtime/object_layout.dart';
/// Can replace instructions with multiple low-level
/// instructions or combine instructions and their inputs
/// into a single low-level instruction.
///
/// TODO: insert boxing/unboxing
final class Lowering extends Pass with DefaultInstructionVisitor<void> {
final FunctionRegistry functionRegistry;
final ObjectLayout objectLayout;
@@ -0,0 +1,171 @@
// 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/constant_value.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/ir/types.dart';
import 'package:cfg/passes/pass.dart';
import 'package:cfg/utils/bit_vector.dart';
/// Insert boxing and unboxing instructions to make sure
/// IR instructions take expected representation of their inputs.
final class Unboxing extends Pass {
late final BitVector _unboxedPhis = BitVector(graph.instructions.length);
Unboxing() : super('Unboxing');
@override
void run() {
_markUnboxedPhis();
var changed = false;
for (final block in graph.reversePostorder) {
currentBlock = block;
for (final instr in block) {
currentInstruction = instr;
for (int i = 0, n = instr.inputCount; i < n; ++i) {
final input = instr.inputDefAt(i);
final isDefUnboxed = hasUnboxedResult(input);
final isInputUnboxed = hasUnboxedInput(instr, i);
if (isDefUnboxed != isInputUnboxed) {
_convertInput(instr, i, input, isDefUnboxed);
changed = true;
}
}
}
}
if (changed) {
graph.invalidateInstructionNumbering();
}
}
/// Returns true if [instr] takes unboxed value as [inputIndex]-th input.
bool hasUnboxedInput(Instruction instr, int inputIndex) {
return switch (instr) {
Phi() => _unboxedPhis[instr.id],
Comparison() => instr.op.isIntComparison || instr.op.isDoubleComparison,
CompareAndBranch() =>
instr.op.isIntComparison || instr.op.isDoubleComparison,
BinaryIntOp() ||
UnaryIntOp() ||
BinaryDoubleOp() ||
UnaryDoubleOp() ||
Box() => true,
StoreField() => false, // TODO: unboxed fields,
CallInstruction() => false, // TODO: support unboxed parameters.
Return() => false, // TODO: support unboxed return values.
_ => false,
};
}
/// Returns true if result of [instr] is an unboxed value.
bool hasUnboxedResult(Definition instr) {
return switch (instr) {
Phi() => _unboxedPhis[instr.id],
BinaryIntOp() ||
UnaryIntOp() ||
BinaryDoubleOp() ||
UnaryDoubleOp() ||
Unbox() => true,
LoadField() => false, // TODO: unboxed fields,
Parameter() => false, // TODO: support unboxed parameters.
CallInstruction() => false, // TODO: support unboxed return values.
_ => false,
};
}
/// Select representation for phis which can be unboxed.
/// Phi is marked as unboxed if it takes at least one unboxed input.
void _markUnboxedPhis() {
for (final block in graph.reversePostorder) {
if (block is! JoinBlock) {
continue;
}
currentBlock = block;
for (final phi in block.phis) {
currentInstruction = phi;
if (!_canBeUnboxed(phi)) {
continue;
}
if (_findUnboxedInput(phi)) {
_unboxedPhis[phi.id] = true;
}
}
}
}
bool _canBeUnboxed(Phi instr) {
final type = instr.type;
return type is IntType || type is DoubleType;
}
/// Find at least one unboxed input while transitively traversing phis.
bool _findUnboxedInput(Phi instr) {
final visited = {instr};
final workList = [instr];
while (workList.isNotEmpty) {
instr = workList.removeLast();
for (int i = 0, n = instr.inputCount; i < n; ++i) {
final input = instr.inputDefAt(i);
if (input == instr) {
continue;
}
if (hasUnboxedResult(input)) {
return true;
}
if (input is Phi && _canBeUnboxed(input)) {
if (visited.add(input)) {
workList.add(input);
}
}
}
}
return false;
}
void _convertInput(
Instruction instr,
int inputIndex,
Definition def,
bool isDefUnboxed,
) {
final type = def.type;
Definition replacement;
if (def is Constant) {
assert(!isDefUnboxed);
replacement = graph.getConstant(
ConstantValue(switch (type) {
IntType() => UnboxedIntConstant(def.value.intValue),
DoubleType() => UnboxedDoubleConstant(def.value.doubleValue),
_ => throw 'Unexpected unboxed type $type',
}),
);
} else {
final insertionPoint = (instr is Phi)
? instr.block!.predecessors[inputIndex].lastInstruction
: instr;
replacement = isDefUnboxed
? switch (type) {
IntType() => BoxInt(graph, insertionPoint.sourcePosition, def),
DoubleType() => BoxDouble(
graph,
insertionPoint.sourcePosition,
def,
),
_ => throw 'Unexpected unboxed type $type',
}
: switch (type) {
IntType() => UnboxInt(graph, insertionPoint.sourcePosition, def),
DoubleType() => UnboxDouble(
graph,
insertionPoint.sourcePosition,
def,
),
_ => throw 'Unexpected unboxed type $type',
};
replacement.insertBefore(insertionPoint);
}
instr.replaceInputAt(inputIndex, replacement);
}
}
@@ -106,6 +106,8 @@ enum ObjectPoolEntryKind {
staticFieldOffset,
interfaceCall,
dynamicCall,
unboxedInt,
unboxedDouble,
}
abstract base class SerializationCluster {
@@ -1292,7 +1294,7 @@ final class ObjectPoolSerializationCluster extends SerializationCluster {
case ReservedEntry():
break;
}
} else {
} else if (entry is! UnboxedConstant) {
serializer.push(entry);
}
}
@@ -1333,6 +1335,12 @@ final class ObjectPoolSerializationCluster extends SerializationCluster {
serializer.writeRefId(icDatas[entry]);
case ReservedEntry():
}
} else if (entry is UnboxedIntConstant) {
serializer.writeUint(ObjectPoolEntryKind.unboxedInt.index);
serializer.out.writeInt(entry.value);
} else if (entry is UnboxedDoubleConstant) {
serializer.writeUint(ObjectPoolEntryKind.unboxedDouble.index);
serializer.out.writeDouble(entry.value);
} else {
serializer.writeUint(ObjectPoolEntryKind.objectRef.index);
serializer.writeRefId(entry);
@@ -9,17 +9,23 @@ import 'package:native_compiler/back_end/arm64/assembler.dart';
import 'package:native_compiler/back_end/assembler.dart';
import 'package:native_compiler/back_end/code.dart';
import 'package:native_compiler/back_end/object_pool.dart';
import 'package:native_compiler/runtime/object_layout.dart';
import 'package:native_compiler/runtime/vm_defs.dart';
import 'package:test/test.dart';
import 'disassembler.dart' show Disassembler;
void main() {
final vmOffsets = Arm64VMOffsets();
final objectLayout = ObjectLayout(
vmOffsets,
wordSize: wordSize,
compressedWordSize: wordSize,
);
final objectPoolBase = vmOffsets.ObjectPool_elementOffset(0);
late Arm64Assembler asm;
setUp(() {
asm = Arm64Assembler(vmOffsets);
asm = Arm64Assembler(vmOffsets, objectLayout);
});
void expectDisassembly(String expected) {
@@ -192,10 +198,14 @@ void main() {
});
test('loadConstant', () {
asm.loadConstant(R0, ConstantValue.fromString('abc'));
asm.loadConstant(R1, ConstantValue.fromInt(42));
asm.loadConstant(R1, ConstantValue(UnboxedIntConstant(42)));
asm.loadConstant(R2, ConstantValue.fromInt(42));
asm.loadConstant(R3, ConstantValue.fromInt(0x7fffffff_ffffffff));
expectDisassembly(
'ldr r0, [pp, #${objectPoolBase}]\n'
'movz r1, #0x2a\n',
'movz r1, #0x2a\n'
'movz r2, #0x54\n'
'ldr r3, [pp, #${objectPoolBase + 8}]\n',
);
});
test('loadImmediate', () {
@@ -337,7 +347,15 @@ void main() {
});
test('inlineAllocation - object size 16', () {
final slowPath = Label();
asm.inlineAllocation(R0, R1, R2, R3, 16, slowPath);
asm.inlineAllocation(
R0,
R1,
R2,
R3,
16,
slowPath,
initializeFields: true,
);
asm.bind(slowPath);
expectDisassembly(
'ldp r0, r2, [thr, #${vmOffsets.Thread_top_offset}]\n'
@@ -350,9 +368,39 @@ void main() {
'add r0, r0, #0x1\n',
);
});
test('inlineAllocation - object size 16, no field initialization', () {
final slowPath = Label();
asm.inlineAllocation(
R0,
R1,
R2,
R3,
16,
slowPath,
initializeFields: false,
);
asm.bind(slowPath);
expectDisassembly(
'ldp r0, r2, [thr, #${vmOffsets.Thread_top_offset}]\n'
'add r3, r0, #0x10\n'
'cmp r2, r3\n'
'bls +16\n'
'str r3, [thr, #${vmOffsets.Thread_top_offset}]\n'
'str r1, [r0]\n'
'add r0, r0, #0x1\n',
);
});
test('inlineAllocation - object size 32', () {
final slowPath = Label();
asm.inlineAllocation(R0, R1, R2, R3, 32, slowPath);
asm.inlineAllocation(
R0,
R1,
R2,
R3,
32,
slowPath,
initializeFields: true,
);
asm.bind(slowPath);
expectDisassembly(
'ldp r0, r2, [thr, #${vmOffsets.Thread_top_offset}]\n'
@@ -368,7 +416,15 @@ void main() {
});
test('inlineAllocation - object size 160', () {
final slowPath = Label();
asm.inlineAllocation(R0, R1, R2, R3, 160, slowPath);
asm.inlineAllocation(
R0,
R1,
R2,
R3,
160,
slowPath,
initializeFields: true,
);
asm.bind(slowPath);
expectDisassembly(
'ldp r0, r2, [thr, #${vmOffsets.Thread_top_offset}]\n'
@@ -626,6 +682,38 @@ void main() {
expectDisassembly('uxth r1, r2\n');
});
test('asr', () {
asm.asr(R0, R1, 0);
asm.asr(R0, R1, 0, .s32);
asm.asr(R1, R2, 1);
asm.asr(R1, R2, 1, .s32);
asm.asr(R4, R2, 31, .s32);
asm.asr(R4, R2, 63);
expectDisassembly(
'mov r0, r1\n'
'movw r0, r1\n'
'asr r1, r2, #1\n'
'asrw r1, r2, #1\n'
'asrw r4, r2, #31\n'
'asr r4, r2, #63\n',
);
expectThrows(() {
asm.asr(R4, R2, -1);
});
expectThrows(() {
asm.asr(R4, R2, 64);
});
expectThrows(() {
asm.asr(R4, R2, 32, .s32);
});
expectThrows(() {
asm.asr(SP, R2, 1);
});
expectThrows(() {
asm.asr(R4, SP, 1);
});
});
test('and', () {
asm.and(R0, R1, R2);
asm.and(R0, R0, Immediate(-512));
@@ -835,6 +923,9 @@ void main() {
expectThrows(() {
asm.ldr(R0, WritebackRegOffsetAddress(R0, 8, isPostIndexed: true));
});
expectThrows(() {
asm.ldr(R0, RegOffsetAddress(R1, 0), .simd128);
});
});
test('str', () {
@@ -881,6 +972,89 @@ void main() {
expectThrows(() {
asm.str(R0, WritebackRegOffsetAddress(R0, 8, isPostIndexed: true));
});
expectThrows(() {
asm.str(R0, RegOffsetAddress(R1, 0), .simd128);
});
});
test('fldr', () {
asm.fldr(V0, RegOffsetAddress(R1, 7));
asm.fldr(V1, RegOffsetAddress(R1, 7), .s16);
asm.fldr(V2, RegOffsetAddress(R1, 32), .s32);
asm.fldr(V3, RegOffsetAddress(R1, -5), .s64);
asm.fldr(V4, RegOffsetAddress(SP, 32768), .simd128);
asm.fldr(V5, WritebackRegOffsetAddress(R1, 16, isPostIndexed: true));
asm.fldr(
V6,
WritebackRegOffsetAddress(R1, -8, isPostIndexed: false),
.simd128,
);
asm.fldr(V0, WritebackRegOffsetAddress(R0, 8, isPostIndexed: true), .s32);
expectDisassembly(
'fldrd v0, [r1, #7]\n'
'fldrh v1, [r1, #7]\n'
'fldrs v2, [r1, #32]\n'
'fldrd v3, [r1, #-5]\n'
'fldrq v4, [csp, #32768]\n'
'fldrd v5, [r1], #16 !\n'
'fldrq v6, [r1, #-8]!\n'
'fldrs v0, [r0], #8 !\n',
);
expectThrows(() {
asm.fldr(V0, RegOffsetAddress(R1, 32768));
});
expectThrows(() {
asm.fldr(V0, RegOffsetAddress(R1, 4097));
});
expectThrows(() {
asm.fldr(V0, RegOffsetAddress(R1, -512));
});
expectThrows(() {
asm.fldr(V0, WritebackRegOffsetAddress(R1, 512, isPostIndexed: true));
});
expectThrows(() {
asm.fldr(V0, WritebackRegOffsetAddress(R1, -513, isPostIndexed: false));
});
});
test('fstr', () {
asm.fstr(V0, RegOffsetAddress(R1, 7));
asm.fstr(V1, RegOffsetAddress(R1, 7), .s16);
asm.fstr(V2, RegOffsetAddress(R1, 32), .s32);
asm.fstr(V3, RegOffsetAddress(R1, -5), .s64);
asm.fstr(V4, RegOffsetAddress(SP, 32768), .simd128);
asm.fstr(V5, WritebackRegOffsetAddress(R1, 16, isPostIndexed: true));
asm.fstr(
V6,
WritebackRegOffsetAddress(R1, -8, isPostIndexed: false),
.simd128,
);
asm.fstr(V0, WritebackRegOffsetAddress(R0, 8, isPostIndexed: true), .s32);
expectDisassembly(
'fstrd v0, [r1, #7]\n'
'fstrh v1, [r1, #7]\n'
'fstrs v2, [r1, #32]\n'
'fstrd v3, [r1, #-5]\n'
'fstrq v4, [csp, #32768]\n'
'fstrd v5, [r1], #16 !\n'
'fstrq v6, [r1, #-8]!\n'
'fstrs v0, [r0], #8 !\n',
);
expectThrows(() {
asm.fstr(V0, RegOffsetAddress(R1, 32768));
});
expectThrows(() {
asm.fstr(V0, RegOffsetAddress(R1, 4097));
});
expectThrows(() {
asm.fstr(V0, RegOffsetAddress(R1, -512));
});
expectThrows(() {
asm.fstr(V0, WritebackRegOffsetAddress(R1, 512, isPostIndexed: true));
});
expectThrows(() {
asm.fstr(V0, WritebackRegOffsetAddress(R1, -513, isPostIndexed: false));
});
});
test('ldp', () {
@@ -824,7 +824,11 @@ class ARM64Decoder {
void printMemOperand(Instr instr) {
final int rn = instr.rnField();
if (instr.bit(24) == 1) {
final int scale = instr.szField();
int scale = instr.szField();
if (instr.bit(26) == 1 && instr.bit(23) == 1 && scale == 0) {
// 128-bit SIMD&FP memory op.
scale = 4;
}
final int imm12 = instr.imm12Field();
final int off = imm12 << scale;
print("[");
+3
View File
@@ -34,6 +34,7 @@ import 'package:native_compiler/back_end/regalloc_checker.dart';
import 'package:native_compiler/back_end/register_allocator.dart';
import 'package:native_compiler/passes/lowering.dart';
import 'package:native_compiler/passes/reorder_blocks.dart';
import 'package:native_compiler/passes/unboxing.dart';
import 'package:native_compiler/runtime/object_layout.dart';
import 'package:native_compiler/runtime/vm_defs.dart';
import 'package:test/test.dart';
@@ -170,6 +171,8 @@ class CompileAndDumpIr extends RecursiveVisitor {
ConstantPropagation(),
ControlFlowOptimizations(),
Lowering(functionRegistry, objectLayout),
Unboxing(),
ValueNumbering(simplification: Simplification()),
ReorderBlocks(backEndState),
LinearScanRegisterAllocator(backEndState, constraints),
RegisterAllocationChecker(backEndState, constraints),
@@ -16,16 +16,15 @@ B0 = EntryBlock()
ParallelMove output(param[1] -> vloc:R1)
v3 = Parameter(x) # RA: param[2] <- ()
ParallelMove output(param[2] -> vloc:R2)
v20 = LoadInstanceField(C.#typeArguments, v2) # RA: R3 <- (R1)
v10 = TypeTest(v3, v9, v20, List<C.T%>) # RA: R3 <- (R2, -, R3)
v20 = LoadInstanceField(C.#typeArguments, v2) # RA: R1 <- (R1)
ParallelMove spill(R1 -> stack[0])
v10 = TypeTest(v3, v9, v20, List<C.T%>) # RA: R3 <- (R2, -, R1)
DirectCall print(v10) # RA: R0 <- (R3) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30]
ParallelMove split(param[0] -> R1, param[2] -> R2)
v13 = TypeTest(v3, v1, v9, List<C.typeParameters.U%>) # RA: R0 <- (R2, R1, -)
DirectCall print(v13) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30]
ParallelMove split(param[1] -> R1)
v19 = LoadInstanceField(C.#typeArguments, v2) # RA: R0 <- (R1)
ParallelMove split(param[0] -> R1, param[2] -> R2)
v16 = TypeCast(v3, v1, v19, Map<C.T%, C.typeParameters.U%>) # RA: R0 <- (R2, R1, R0)
ParallelMove split(param[0] -> R2, param[2] -> R3, stack[0] -> R1)
v16 = TypeCast(v3, v1, v20, Map<C.T%, C.typeParameters.U%>) # RA: R0 <- (R3, R2, R1)
DirectCall print(v16) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30]
ParallelMove input(NullConstant(null) -> R0)
Return(v9) # RA: (R0)
@@ -37,7 +36,9 @@ B0 = EntryBlock()
ParallelMove output(param[0] -> vloc:R0)
v2 = Parameter(b) # RA: param[1] <- ()
ParallelMove output(param[1] -> vloc:R1)
v7 = Comparison int & == 0(v1, v2) # RA: R0 <- (R0, R1)
v9 = UnboxInt(v1) # RA: R0 <- (R0)
v10 = UnboxInt(v2) # RA: R1 <- (R1)
v7 = Comparison int & == 0(v9, v10) # RA: R0 <- (R0, R1)
ParallelMove input(vloc:R0 -> R0)
Return(v7) # RA: (R0)
@@ -51,7 +52,9 @@ B0 = EntryBlock() dominates:(B9, B11, B8)
ParallelMove output(param[0] -> vloc:R0)
v2 = Parameter(b) # RA: param[1] <- ()
ParallelMove output(param[1] -> vloc:R1)
v7 = Comparison int & == 0(v1, v2) # RA: R0 <- (R0, R1)
v30 = UnboxInt(v1) # RA: R0 <- (R0)
v31 = UnboxInt(v2) # RA: R1 <- (R1)
v7 = Comparison int & == 0(v30, v31) # RA: R0 <- (R0, R1)
ParallelMove spill(R0 -> stack[0])
Branch(v7, true: B8, false: B9) # RA: (R0)
B8 = TargetBlock() idom:B0
@@ -79,7 +82,9 @@ B0 = EntryBlock() dominates:(B11, B13, B10)
ParallelMove output(param[0] -> vloc:R0)
v2 = Parameter(b) # RA: param[1] <- ()
ParallelMove output(param[1] -> vloc:R1)
v7 = Comparison int & == 0(v1, v2) # RA: R0 <- (R0, R1)
v20 = UnboxInt(v1) # RA: R0 <- (R0)
v21 = UnboxInt(v2) # RA: R1 <- (R1)
v7 = Comparison int & == 0(v20, v21) # RA: R0 <- (R0, R1)
ParallelMove spill(R0 -> stack[0])
Branch(v7, true: B10, false: B11) # RA: (R0)
B10 = TargetBlock() idom:B0
@@ -103,7 +108,9 @@ B0 = EntryBlock() dominates:(B8, B14, B16, B7)
ParallelMove output(param[1] -> vloc:R1)
v3 = Parameter(c) # RA: param[2] <- ()
ParallelMove output(param[2] -> vloc:R2)
v6 = Comparison int >(v1, v2) # RA: R0 <- (R0, R1)
v35 = UnboxInt(v1) # RA: R0 <- (R0)
v36 = UnboxInt(v2) # RA: R1 <- (R1)
v6 = Comparison int >(v35, v36) # RA: R0 <- (R0, R1)
ParallelMove spill(R0 -> stack[0])
Branch(v6, true: B7, false: B8) # RA: (R0)
B7 = TargetBlock() idom:B0
@@ -131,40 +138,50 @@ B28 = JoinBlock(B26, B25) idom:B14
--- test5
B0 = EntryBlock() dominates:(B7, B6)
v3 = Constant(32)
Constant(32)
v11 = Constant(1)
v14 = Constant(16)
v16 = Constant(8)
Constant(16)
Constant(8)
v23 = Constant(2)
v25 = Constant(257)
Constant(257)
v37 = Constant(3)
v47 = Constant(4)
v50 = Constant(7)
Constant(7)
v58 = Constant(5)
v62 = Constant(-1)
v69 = Constant(UnboxedIntConstant(32))
v71 = Constant(UnboxedIntConstant(16))
v72 = Constant(UnboxedIntConstant(8))
v74 = Constant(UnboxedIntConstant(257))
v77 = Constant(UnboxedIntConstant(7))
v1 = Parameter(a) # RA: param[0] <- ()
ParallelMove output(param[0] -> vloc:R0)
CompareAndBranch int & != 0(v1, v3, true: B6, false: B7) # RA: (R0, -)
v68 = UnboxInt(v1) # RA: R1 <- (R0)
CompareAndBranch int & != 0(v68, v69, true: B6, false: B7) # RA: (R1, -)
B6 = TargetBlock() idom:B0
ParallelMove input(IntConstant(1) -> R0)
Return(v11) # RA: (R0)
B7 = TargetBlock() idom:B0 dominates:(B19, B18)
v15 = BinaryIntOp &(v1, v14) # RA: R1 <- (R0, -)
CompareAndBranch int ==(v15, v16, true: B18, false: B19) # RA: (R1, -)
v70 = UnboxInt(v1) # RA: R1 <- (R0)
v15 = BinaryIntOp &(v70, v71) # RA: R1 <- (R1, -)
CompareAndBranch int ==(v15, v72, true: B18, false: B19) # RA: (R1, -)
B18 = TargetBlock() idom:B7
ParallelMove input(IntConstant(2) -> R0)
Return(v23) # RA: (R0)
B19 = TargetBlock() idom:B7 dominates:(B33, B32)
v29 = BinaryIntOp &(v1, v25) # RA: R1 <- (R0, -)
CompareAndBranch int ==(v29, v25, true: B32, false: B33) # RA: (R1, -)
v73 = UnboxInt(v1) # RA: R1 <- (R0)
v29 = BinaryIntOp &(v73, v74) # RA: R1 <- (R1, -)
CompareAndBranch int ==(v29, v74, true: B32, false: B33) # RA: (R1, -)
B32 = TargetBlock() idom:B19
ParallelMove input(IntConstant(3) -> R0)
Return(v37) # RA: (R0)
B33 = TargetBlock() idom:B19 dominates:(B43, B42)
CompareAndBranch int & != 0(v1, v16, true: B42, false: B43) # RA: (R0, -)
v75 = UnboxInt(v1) # RA: R1 <- (R0)
CompareAndBranch int & != 0(v75, v72, true: B42, false: B43) # RA: (R1, -)
B42 = TargetBlock() idom:B33 dominates:(B54, B53)
v51 = BinaryIntOp &(v1, v50) # RA: R0 <- (R0, -)
CompareAndBranch int ==(v51, v50, true: B53, false: B54) # RA: (R0, -)
v76 = UnboxInt(v1) # RA: R0 <- (R0)
v51 = BinaryIntOp &(v76, v77) # RA: R0 <- (R0, -)
CompareAndBranch int ==(v51, v77, true: B53, false: B54) # RA: (R0, -)
B53 = TargetBlock() idom:B42
ParallelMove input(IntConstant(-1) -> R0)
Return(v62) # RA: (R0)
@@ -30,21 +30,25 @@ B16 = TargetBlock() idom:B7
--- test2
B0 = EntryBlock() dominates:(B3)
v1 = Constant(0)
v6 = Constant(10)
v12 = Constant(3)
Constant(0)
Constant(10)
Constant(3)
v19 = Constant("done")
v21 = Constant(null)
v24 = Constant(1)
ParallelMove control(IntConstant(0) -> R0)
Constant(1)
v32 = Constant(UnboxedIntConstant(0))
v33 = Constant(UnboxedIntConstant(10))
v34 = Constant(UnboxedIntConstant(3))
v35 = Constant(UnboxedIntConstant(1))
ParallelMove control(UnboxedIntConstant(0) -> R0)
Goto(B3)
B3 = JoinBlock(B0, B15) idom:B0 dominates:(B9, B8) loop-header (depth:1 body:(B3, B8, B15) back-edges:(B15))
v29 = Phi(v1, v25) # RA: R0 <- (-, R0)
CompareAndBranch int <(v29, v6, true: B8, false: B9) # RA: (R0, -)
v29 = Phi(v32, v25) # RA: R0 <- (-, R0)
CompareAndBranch int <(v29, v33, true: B8, false: B9) # RA: (R0, -)
B8 = TargetBlock() idom:B3 dominates:(B15, B14) in-loop:B3
CompareAndBranch int >(v29, v12, true: B14, false: B15) # RA: (R0, -)
CompareAndBranch int >(v29, v34, true: B14, false: B15) # RA: (R0, -)
B15 = TargetBlock() idom:B8 in-loop:B3
v25 = BinaryIntOp +(v29, v24) # RA: R0 <- (R0, -)
v25 = BinaryIntOp +(v29, v35) # RA: R0 <- (R0, -)
Goto(B3)
B14 = TargetBlock() idom:B8
DirectCall print(v19) # RA: R0 <- (-) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30]
@@ -56,28 +60,31 @@ B9 = TargetBlock() idom:B3
--- test3
B0 = EntryBlock() dominates:(B3)
v1 = Constant(0)
v6 = Constant(10)
Constant(0)
Constant(10)
v28 = Constant("done")
v33 = Constant(1)
Constant(1)
v42 = Constant(null)
ParallelMove control(IntConstant(0) -> R0)
v49 = Constant(UnboxedIntConstant(0))
v50 = Constant(UnboxedIntConstant(10))
v51 = Constant(UnboxedIntConstant(1))
ParallelMove control(UnboxedIntConstant(0) -> R0)
Goto(B3)
B3 = JoinBlock(B0, B30) idom:B0 dominates:(B9, B8) loop-header (depth:1 body:(B3, B8, B12, B17, B30, B16, B24, B23) back-edges:(B30))
v44 = Phi(v1, v39) # RA: R0 <- (-, R0)
v44 = Phi(v49, v39) # RA: R0 <- (-, R0)
ParallelMove spill(R0 -> stack[0])
CompareAndBranch int <(v44, v6, true: B8, false: B9) # RA: (R0, -)
CompareAndBranch int <(v44, v50, true: B8, false: B9) # RA: (R0, -)
B8 = TargetBlock() idom:B3 dominates:(B12) in-loop:B3
ParallelMove control(IntConstant(0) -> R1)
ParallelMove control(UnboxedIntConstant(0) -> R1)
Goto(B12)
B12 = JoinBlock(B8, B24) idom:B8 dominates:(B17, B30, B16) loop-header (depth:2 body:(B12, B16, B24) back-edges:(B24))
v45 = Phi(v1, v34) # RA: R1 <- (-, R1)
CompareAndBranch int <(v45, v6, true: B16, false: B17) # RA: (R1, -)
v45 = Phi(v49, v34) # RA: R1 <- (-, R1)
CompareAndBranch int <(v45, v50, true: B16, false: B17) # RA: (R1, -)
B16 = TargetBlock() idom:B12 dominates:(B24, B23) in-loop:B12
v21 = BinaryIntOp +(v44, v45) # RA: R2 <- (R0, R1)
CompareAndBranch int >(v21, v6, true: B23, false: B24) # RA: (R2, -)
CompareAndBranch int >(v21, v50, true: B23, false: B24) # RA: (R2, -)
B24 = TargetBlock() idom:B16 in-loop:B12
v34 = BinaryIntOp +(v45, v33) # RA: R1 <- (R1, -)
v34 = BinaryIntOp +(v45, v51) # RA: R1 <- (R1, -)
Goto(B12)
B23 = TargetBlock() idom:B16 in-loop:B3
DirectCall print(v28) # RA: R0 <- (-) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30]
@@ -86,7 +93,7 @@ B17 = TargetBlock() idom:B12 in-loop:B3
Goto(B30)
B30 = JoinBlock(B17, B23) idom:B12 in-loop:B3
ParallelMove split(stack[0] -> R0)
v39 = BinaryIntOp +(v44, v33) # RA: R0 <- (R0, -)
v39 = BinaryIntOp +(v44, v51) # RA: R0 <- (R0, -)
ParallelMove spill(R0 -> stack[0])
Goto(B3)
B9 = TargetBlock() idom:B3
@@ -0,0 +1,29 @@
// 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.
int test1(int a, int b) => a + b;
int test2(int n) {
var sum = 0;
for (var i = 0; i < n; i++) {
sum += i;
}
return sum;
}
int test3(int a1, int a2, bool c1, bool c2) {
int b;
if (c1) {
b = a1 + 1;
} else {
if (c2) {
b = 5;
} else {
b = a2;
}
}
return b * 2;
}
void main() {}
@@ -0,0 +1,80 @@
--- test1
B0 = EntryBlock()
v1 = Parameter(a) # RA: param[0] <- ()
ParallelMove output(param[0] -> vloc:R0)
v2 = Parameter(b) # RA: param[1] <- ()
ParallelMove output(param[1] -> vloc:R1)
v7 = UnboxInt(v1) # RA: R0 <- (R0)
v8 = UnboxInt(v2) # RA: R1 <- (R1)
v5 = BinaryIntOp +(v7, v8) # RA: R0 <- (R0, R1)
v9 = BoxInt(v5) # RA: R0 <- (R0) temps: [R1, R2, R3]
ParallelMove input(vloc:R0 -> R0)
Return(v9) # RA: (R0)
--- test2
B0 = EntryBlock() dominates:(B5)
Constant(0)
Constant(1)
v27 = Constant(UnboxedIntConstant(0))
v29 = Constant(UnboxedIntConstant(1))
v1 = Parameter(n) # RA: param[0] <- ()
ParallelMove output(param[0] -> vloc:R0)
ParallelMove control(UnboxedIntConstant(0) -> R2, UnboxedIntConstant(0) -> R1)
Goto(B5)
B5 = JoinBlock(B0, B10) idom:B0 dominates:(B11, B10) loop-header (depth:1 body:(B5, B10) back-edges:(B10))
v25 = Phi(v27, v19) # RA: R2 <- (-, R2)
v24 = Phi(v27, v15) # RA: R1 <- (-, R1)
v28 = UnboxInt(v1) # RA: R3 <- (R0)
CompareAndBranch int <(v25, v28, true: B10, false: B11) # RA: (R2, R3)
B10 = TargetBlock() idom:B5 in-loop:B5
v15 = BinaryIntOp +(v24, v25) # RA: R1 <- (R1, R2)
v19 = BinaryIntOp +(v25, v29) # RA: R2 <- (R2, -)
Goto(B5)
B11 = TargetBlock() idom:B5
v30 = BoxInt(v24) # RA: R0 <- (R1) temps: [R1, R2, R3]
ParallelMove input(vloc:R0 -> R0)
Return(v30) # RA: (R0)
--- test3
B0 = EntryBlock() dominates:(B7, B13, B6)
Constant(1)
v19 = Constant(5)
Constant(2)
v35 = Constant(UnboxedIntConstant(1))
v1 = Parameter(a1) # RA: param[0] <- ()
ParallelMove output(param[0] -> vloc:R0)
v2 = Parameter(a2) # RA: param[1] <- ()
ParallelMove output(param[1] -> vloc:R3)
v3 = Parameter(c1) # RA: param[2] <- ()
ParallelMove output(param[2] -> vloc:R1)
v4 = Parameter(c2) # RA: param[3] <- ()
ParallelMove output(param[3] -> vloc:R2)
Branch(v3, true: B6, false: B7) # RA: (R1)
B6 = TargetBlock() idom:B0
v34 = UnboxInt(v1) # RA: R0 <- (R0)
v11 = BinaryIntOp +(v34, v35) # RA: R0 <- (R0, -)
Goto(B13)
B7 = TargetBlock() idom:B0 dominates:(B17, B21, B16)
Branch(v4, true: B16, false: B17) # RA: (R2)
B16 = TargetBlock() idom:B7
ParallelMove control(IntConstant(5) -> R3)
Goto(B21)
B17 = TargetBlock() idom:B7
Goto(B21)
B21 = JoinBlock(B17, B16) idom:B7
v32 = Phi(v2, v19) # RA: R3 <- (R3, -)
v36 = UnboxInt(v32) # RA: R0 <- (R3)
Goto(B13)
B13 = JoinBlock(B21, B6) idom:B0
v31 = Phi(v36, v11) # RA: R0 <- (R0, R0)
v33 = BinaryIntOp <<(v31, v35) # RA: R0 <- (R0, -)
v37 = BoxInt(v33) # RA: R0 <- (R0) temps: [R1, R2, R3]
ParallelMove input(vloc:R0 -> R0)
Return(v37) # RA: (R0)
--- main
B0 = EntryBlock()
v1 = Constant(null)
ParallelMove input(NullConstant(null) -> R0)
Return(v1) # RA: (R0)
+14
View File
@@ -100,6 +100,8 @@ class ModuleSnapshot : public AllStatic {
kStaticFieldOffset,
kInterfaceCall,
kDynamicCall,
kUnboxedInt,
kUnboxedDouble,
};
};
@@ -1196,6 +1198,18 @@ class ObjectPoolDeserializationCluster : public DeserializationCluster {
entry2.raw_obj_ = StubCode::OneArgOptimizedCheckInlineCache().ptr();
break;
}
case ModuleSnapshot::kUnboxedInt: {
pool->untag()->entry_bits()[j] = immediate_entry_bits;
UntaggedObjectPool::Entry& entry = pool->untag()->data()[j];
entry.raw_value_ = d.Read<int64_t>();
break;
}
case ModuleSnapshot::kUnboxedDouble: {
pool->untag()->entry_bits()[j] = immediate_entry_bits;
UntaggedObjectPool::Entry& entry = pool->untag()->data()[j];
entry.raw_value_ = bit_cast<int64_t>(d.Read<double>());
break;
}
}
}
}