[modular_aot] Optional and named parameters

Issue: https://github.com/dart-lang/sdk/issues/61635
Change-Id: Ib8a4cbb7312110b7daa82ca40555b452b0599549
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/485200
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Alexander Markov
2026-03-05 15:51:29 -08:00
committed by Commit Queue
parent 30783ff6e2
commit 8a6955a272
10 changed files with 409 additions and 66 deletions
+11 -5
View File
@@ -208,7 +208,7 @@ class AstToIr extends ast.RecursiveVisitor {
if (!field.isStatic) {
if (field.isLate) {
if (!initializedFields.contains(field)) {
throw 'Unimplemented: _initLateInstanceField';
throw 'Unimplemented: initialization of late instance field';
}
} else {
final fieldInitializer = field.initializer;
@@ -499,7 +499,7 @@ class AstToIr extends ast.RecursiveVisitor {
if (!enableAsserts) {
return;
}
throw 'unimplemented';
throw 'Unsupported node ${node.runtimeType} with enabled asserts';
}
@override
@@ -742,7 +742,9 @@ class AstToIr extends ast.RecursiveVisitor {
@override
void visitVariableDeclaration(ast.VariableDeclaration node) {
if (node.isLate) throw 'unimplemented';
if (node.isLate) {
throw 'Unsupported node ${node.runtimeType} for late variable';
}
if (node.isConst) return;
final local = localVarIndexer.variableForDeclaration(node);
final initializer = node.initializer;
@@ -762,7 +764,9 @@ class AstToIr extends ast.RecursiveVisitor {
@override
void visitVariableGet(ast.VariableGet node) {
final variable = node.variable;
if (variable.isLate) throw 'unimplemented';
if (variable.isLate) {
throw 'Unsupported node ${node.runtimeType} for late variable';
}
if (variable.isConst) {
builder.addConstant(
ConstantValue(
@@ -789,7 +793,9 @@ class AstToIr extends ast.RecursiveVisitor {
@override
void visitVariableSet(ast.VariableSet node) {
final variable = node.variable;
if (variable.isLate) throw 'unimplemented';
if (variable.isLate) {
throw 'Unsupported node ${node.runtimeType} for late variable';
}
_translateNode(node.value);
if (_handleUnreachableExpression(1)) return;
final local = localVarIndexer.variableForDeclaration(variable);
+84 -45
View File
@@ -2,6 +2,7 @@
// 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/source_position.dart';
import 'package:cfg/ir/types.dart';
import 'package:cfg/utils/misc.dart';
@@ -22,6 +23,8 @@ sealed class CFunction {
CFunction._(this.member);
ast.FunctionNode? get functionNode => member.function;
/// Whether this function has a receiver parameter.
bool get hasReceiverParameter =>
member.isInstanceMember || member is ast.Constructor;
@@ -36,12 +39,62 @@ sealed class CFunction {
/// Whether this function has function type parameters.
bool get hasFunctionTypeParameters =>
member is ast.Procedure && member.function!.typeParameters.isNotEmpty;
functionNode?.typeParameters.isNotEmpty ?? false;
/// Total number of parameters including function type parameters
/// (represented with a single parameter), receiver, closure and
/// Number of implicit parameters of this function:
/// - function type parameters (represented with a single parameter),
/// - receiver,
/// - closure.
int get numberOfImplicitParameters =>
(hasFunctionTypeParameters ? 1 : 0) +
(hasReceiverParameter ? 1 : 0) +
(hasClosureParameter ? 1 : 0);
/// Number of required positional parameters including implicit parameters.
int get numberOfRequiredPositionalParameters =>
numberOfImplicitParameters + (functionNode?.requiredParameterCount ?? 0);
/// Total number of parameters including implicit parameters and
/// optional parameters.
int get numberOfParameters;
int get numberOfParameters =>
numberOfImplicitParameters +
(functionNode?.positionalParameters.length ?? 0) +
(functionNode?.namedParameters.length ?? 0);
/// Whether this function has optional positional parameters.
bool get hasOptionalPositionalParameters =>
functionNode != null &&
(functionNode!.requiredParameterCount <
functionNode!.positionalParameters.length);
/// Whether this function has named parameters.
bool get hasNamedParameters =>
functionNode?.namedParameters.isNotEmpty ?? false;
ast.VariableDeclaration _getOptionalOrNamedParameter(int index) =>
hasOptionalPositionalParameters
? functionNode!.positionalParameters[index - numberOfImplicitParameters]
: functionNode!.namedParameters[index -
numberOfRequiredPositionalParameters];
/// Default value of the [index]-th optional or named parameter.
ConstantValue getParameterDefaultValue(int index) => ConstantValue(
(_getOptionalOrNamedParameter(index).initializer as ast.ConstantExpression)
.constant,
);
/// Name of the [index]-th named parameter.
/// Named parameters are sorted by name and follow required positional parameters.
String getParameterName(int index) {
assert(hasNamedParameters);
return _getOptionalOrNamedParameter(index).name!;
}
/// Whether the [index]-th named parameter is required.
bool isRequiredParameter(int index) {
assert(hasNamedParameters);
return _getOptionalOrNamedParameter(index).isRequired;
}
/// Return type of this function.
CType get returnType;
@@ -55,7 +108,13 @@ final class GetterFunction extends CFunction {
GetterFunction._(super.member) : assert(member.hasGetter), super._();
@override
int get numberOfParameters => member.isInstanceMember ? 1 /* receiver */ : 0;
ast.FunctionNode? get functionNode => null;
@override
int get numberOfRequiredPositionalParameters => numberOfImplicitParameters;
@override
int get numberOfParameters => numberOfImplicitParameters;
@override
late final CType returnType = CType.fromStaticType(member.getterType);
@@ -74,8 +133,14 @@ final class SetterFunction extends CFunction {
SetterFunction._(super.member) : assert(member.hasSetter), super._();
@override
int get numberOfParameters =>
member.isInstanceMember ? 2 /* receiver, value */ : 1 /* only value */;
ast.FunctionNode? get functionNode => null;
@override
int get numberOfRequiredPositionalParameters =>
numberOfImplicitParameters + 1 /* value */;
@override
int get numberOfParameters => numberOfImplicitParameters + 1 /* value */;
@override
CType get returnType => const TopType(const ast.VoidType());
@@ -99,7 +164,13 @@ final class FieldInitializerFunction extends CFunction {
super._();
@override
int get numberOfParameters => member.isInstanceMember ? 1 /* receiver */ : 0;
ast.FunctionNode? get functionNode => null;
@override
int get numberOfRequiredPositionalParameters => numberOfImplicitParameters;
@override
int get numberOfParameters => numberOfImplicitParameters;
@override
CType get returnType => CType.fromStaticType(member.getterType);
@@ -116,16 +187,7 @@ final class RegularFunction extends CFunction {
super._();
@override
int get numberOfParameters =>
(hasFunctionTypeParameters ? 1 : 0) +
(hasReceiverParameter ? 1 : 0) +
member.function!.positionalParameters.length +
member.function!.namedParameters.length;
@override
late final CType returnType = CType.fromStaticType(
member.function!.returnType,
);
late final CType returnType = CType.fromStaticType(functionNode!.returnType);
@override
String toString() => member.toString();
@@ -135,12 +197,6 @@ final class RegularFunction extends CFunction {
final class GenerativeConstructor extends CFunction {
GenerativeConstructor._(ast.Constructor super.member) : super._();
@override
int get numberOfParameters =>
1 /* receiver */ +
member.function!.positionalParameters.length +
member.function!.namedParameters.length;
@override
CType get returnType => const TopType(const ast.VoidType());
@@ -164,24 +220,14 @@ final class LocalFunction extends ClosureFunction {
final ast.LocalFunction localFunction;
LocalFunction._(super.member, this.localFunction) : super._();
@override
ast.FunctionNode? get functionNode => localFunction.function;
@override
String toString() => 'closure $localFunction at $member';
@override
bool get hasFunctionTypeParameters =>
localFunction.function.typeParameters.isNotEmpty;
@override
int get numberOfParameters =>
(hasFunctionTypeParameters ? 1 : 0) +
1 /* closure */ +
localFunction.function.positionalParameters.length +
localFunction.function.namedParameters.length;
@override
late final CType returnType = CType.fromStaticType(
localFunction.function.returnType,
);
late final CType returnType = CType.fromStaticType(functionNode!.returnType);
@override
SourcePosition get sourcePosition => SourcePosition(localFunction.fileOffset);
@@ -202,13 +248,6 @@ final class TearOffFunction extends ClosureFunction {
? member.enclosingClass!.typeParameters.isNotEmpty
: member.function!.typeParameters.isNotEmpty;
@override
int get numberOfParameters =>
(hasFunctionTypeParameters ? 1 : 0) +
1 /* closure */ +
member.function!.positionalParameters.length +
member.function!.namedParameters.length;
@override
late final CType returnType = CType.fromStaticType(
member is ast.Constructor
@@ -125,6 +125,18 @@ final allocatableRegisters = allRegisters
.where((r) => !reservedRegisters.contains(r))
.toList();
final argumentRegisters = allocatableRegisters
.where((r) => r != argumentsDescriptorReg)
.take(16)
.toList();
final prologueScratchRegisters = allocatableRegisters
.where(
(r) =>
r != argumentsDescriptorReg && r.index > argumentRegisters.last.index,
)
.toList();
/// Floating-point registers.
const FPRegister V0 = FPRegister(0, 'V0');
const FPRegister V1 = FPRegister(1, 'V1');
@@ -2,6 +2,8 @@
// 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:math' as math;
import 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/field.dart';
import 'package:cfg/ir/functions.dart';
@@ -11,6 +13,7 @@ import 'package:cfg/ir/types.dart';
import 'package:cfg/utils/misc.dart';
import 'package:kernel/ast.dart' as ast;
import 'package:native_compiler/back_end/arm64/assembler.dart';
import 'package:native_compiler/back_end/arm64/stack_frame.dart';
import 'package:native_compiler/back_end/arm64/stub_code_generator.dart';
import 'package:native_compiler/back_end/assembler.dart';
import 'package:native_compiler/back_end/code_generator.dart';
@@ -36,6 +39,234 @@ final class Arm64CodeGenerator extends CodeGenerator {
stackPointerReg,
stackFrame.frameSizeToAllocate,
);
final function = graph.function;
if (function.hasOptionalPositionalParameters) {
_prepareOptionalPositionalParameters(function);
} else if (function.hasNamedParameters) {
_prepareNamedParameters(function);
}
}
/// Load positional required and optional arguments into argument registers,
/// filling in the default values if optional arguments are not passed.
/// Extra arguments are copied to the shadow parameters area on the stack.
void _prepareOptionalPositionalParameters(CFunction function) {
final argCountReg = prologueScratchRegisters[0];
final argPtrReg = prologueScratchRegisters[1];
final numRequired = function.numberOfRequiredPositionalParameters;
final total = function.numberOfParameters;
assert(numRequired < total);
// TODO: compressed pointers
// Load total number of arguments as a Smi.
_asm.ldr(
argCountReg,
_asm.fieldAddress(
argumentsDescriptorReg,
vmOffsets.ArgumentsDescriptor_count_offset,
),
);
// Arguments pointer points to the first pair of arguments.
assert(Arm64StackFrame.lastParameterOffsetFromFP == 2 * wordSize);
_asm.add(
argPtrReg,
FP,
ShiftedRegOperand(argCountReg, .LSL, log2wordSize - smiShift),
);
// Label for each number of optional arguments passed.
final labels = List.generate(total - numRequired, (_) => Label());
var i = 0;
final int numArgsToLoadInPairs = math.min(total, argumentRegisters.length);
for (; i + 1 < numArgsToLoadInPairs; i += 2) {
if (i >= numRequired) {
_asm.cmp(argCountReg, Immediate((i + 1) << smiShift));
_asm.b(labels[i - numRequired], .less);
}
// TODO: pass arguments on registers and avoid these loads
_asm.ldp(
argumentRegisters[i + 1],
argumentRegisters[i],
_asm.pairAddress(argPtrReg, -i * wordSize),
);
if (i >= numRequired) {
_asm.b(labels[i + 1 - numRequired], .equal);
} else if (i + 1 >= numRequired) {
_asm.cmp(argCountReg, Immediate((i + 1) << smiShift));
_asm.b(labels[i + 1 - numRequired], .equal);
}
}
for (; i < total; ++i) {
if (i >= numRequired) {
_asm.cmp(argCountReg, Immediate(i << smiShift));
_asm.b(labels[i - numRequired], .equal);
}
final reg = (i < argumentRegisters.length)
? argumentRegisters[i]
: tempReg;
_asm.ldr(reg, _asm.address(argPtrReg, -(i - 1) * wordSize));
if (i >= argumentRegisters.length) {
_asm.str(
reg,
_asm.address(FP, stackFrame.shadowParameterOffsetFromFP(i)),
);
}
}
final done = Label();
_asm.b(done);
for (var i = numRequired; i < total; ++i) {
_asm.bind(labels[i - numRequired]);
final reg = (i < argumentRegisters.length)
? argumentRegisters[i]
: tempReg;
_asm.loadConstant(reg, function.getParameterDefaultValue(i));
if (i >= argumentRegisters.length) {
_asm.str(
reg,
_asm.address(FP, stackFrame.shadowParameterOffsetFromFP(i)),
);
}
}
_asm.bind(done);
}
/// Load required positional and named arguments into argument registers,
/// filling in the default values if optional arguments are not passed.
/// Extra arguments are copied to the shadow parameters area on the stack.
void _prepareNamedParameters(CFunction function) {
final argPtrReg = prologueScratchRegisters[0];
final argNameReg = prologueScratchRegisters[1];
final numRequired = function.numberOfRequiredPositionalParameters;
final total = function.numberOfParameters;
assert(numRequired < total);
// TODO: compressed pointers
// Load total number of arguments as a Smi.
_asm.ldr(
tempReg,
_asm.fieldAddress(
argumentsDescriptorReg,
vmOffsets.ArgumentsDescriptor_count_offset,
),
);
// Arguments pointer points to the first pair of arguments.
assert(Arm64StackFrame.lastParameterOffsetFromFP == 2 * wordSize);
_asm.add(
argPtrReg,
FP,
ShiftedRegOperand(tempReg, .LSL, log2wordSize - smiShift),
);
var i = 0;
final int numArgsToLoadInPairs = math.min(
numRequired,
argumentRegisters.length,
);
for (; i + 1 < numArgsToLoadInPairs; i += 2) {
// TODO: pass arguments on registers and avoid these loads
_asm.ldp(
argumentRegisters[i + 1],
argumentRegisters[i],
_asm.pairAddress(argPtrReg, -i * wordSize),
);
}
for (; i < numRequired; ++i) {
final reg = (i < argumentRegisters.length)
? argumentRegisters[i]
: tempReg;
_asm.ldr(reg, _asm.address(argPtrReg, -(i - 1) * wordSize));
if (i >= argumentRegisters.length) {
_asm.str(
reg,
_asm.address(FP, stackFrame.shadowParameterOffsetFromFP(i)),
);
}
}
// Each argument entry has 2 words: name and position.
assert(vmOffsets.ArgumentsDescriptor_name_offset == 0);
assert(vmOffsets.ArgumentsDescriptor_position_offset == wordSize);
assert(vmOffsets.ArgumentsDescriptor_named_entry_size == 2 * wordSize);
// argumentsDescriptorReg points to the position field of the current argument.
_asm.add(
argumentsDescriptorReg,
argumentsDescriptorReg,
Immediate(
vmOffsets.ArgumentsDescriptor_first_named_entry_offset +
vmOffsets.ArgumentsDescriptor_position_offset,
),
);
if (!function.isRequiredParameter(numRequired)) {
// Load name of the first optional named parameter.
_asm.ldr(
argNameReg,
RegOffsetAddress(
argumentsDescriptorReg,
-vmOffsets.ArgumentsDescriptor_position_offset +
vmOffsets.ArgumentsDescriptor_name_offset,
),
);
}
for (i = numRequired; i < total; ++i) {
Label? proceed;
final destReg = (i < argumentRegisters.length)
? argumentRegisters[i]
: tempReg;
if (!function.isRequiredParameter(i)) {
_asm.loadFromPool(tempReg, function.getParameterName(i));
_asm.cmp(argNameReg, tempReg);
final passed = Label();
_asm.b(passed, .equal);
_asm.loadConstant(destReg, function.getParameterDefaultValue(i));
proceed = Label();
_asm.b(proceed);
_asm.bind(passed);
}
if (i + 1 < total && !function.isRequiredParameter(i + 1)) {
// Load both position of this argument and the name of the next argument.
_asm.ldp(
tempReg,
argNameReg,
WritebackRegOffsetAddress(
argumentsDescriptorReg,
vmOffsets.ArgumentsDescriptor_named_entry_size,
isPostIndexed: true,
),
);
} else {
// Only load the position of this argument.
_asm.ldr(
tempReg,
WritebackRegOffsetAddress(
argumentsDescriptorReg,
vmOffsets.ArgumentsDescriptor_named_entry_size,
isPostIndexed: true,
),
);
}
_asm.sub(
tempReg,
argPtrReg,
ShiftedRegOperand(tempReg, .LSL, log2wordSize - smiShift),
);
_asm.ldr(destReg, RegOffsetAddress(tempReg, wordSize));
if (proceed != null) {
_asm.bind(proceed);
}
if (i >= argumentRegisters.length) {
_asm.str(
destReg,
_asm.address(FP, stackFrame.shadowParameterOffsetFromFP(i)),
);
}
}
}
void _generateBranch(
@@ -65,16 +65,35 @@ final class Arm64Constraints extends Constraints {
// TODO: pass arguments on registers
Constraint parameterConstraint(Parameter instr) {
final paramIndex = instr.variable.index;
final numParams = instr.graph.function.numberOfParameters;
final function = instr.graph.function;
final numParams = function.numberOfParameters;
assert(0 <= paramIndex && paramIndex < numParams);
Constraint? paramConstraint = _parameters?[paramIndex];
if (paramConstraint != null) {
return paramConstraint;
}
if (function.hasOptionalPositionalParameters ||
function.hasNamedParameters) {
if (paramIndex < argumentRegisters.length) {
paramConstraint = argumentRegisters[paramIndex];
} else {
paramConstraint = ParameterStackLocation(
paramIndex - argumentRegisters.length,
registerClass(instr),
);
}
} else {
paramConstraint = ParameterStackLocation(
paramIndex,
registerClass(instr),
);
}
final parameters = (_parameters ??= List<Constraint?>.filled(
numParams,
null,
));
return parameters[paramIndex] ??= ParameterStackLocation(
paramIndex,
registerClass(instr),
);
parameters[paramIndex] = paramConstraint;
return paramConstraint;
}
@override
@@ -19,6 +19,7 @@ import 'package:native_compiler/back_end/stack_frame.dart';
/// FP -> [saved FP]
/// [Code]
/// [saved tagged ObjectPool]
/// [shadow space for optional parameters]
/// [spill slot 0]
/// ...
/// [spill slot M]
@@ -36,12 +37,23 @@ final class Arm64StackFrame extends StackFrame {
/// Offset of the saved pool pointer relative to FP.
static const int poolPointerOffsetFromFP = -2 * wordSize;
/// Offset of the first spill slot, relative to FP
static const int firstSpillSlotOffsetFromFP = -3 * wordSize;
/// Offset of the first shadow parameter, relative to FP
static const int shadowParametersOffsetFromFP = -3 * wordSize;
/// Stack frame alignment.
static const int alignment = 2 * wordSize;
/// Number of stack slots reserved for shadow parameters.
late final int _shadowParametersStackSlots =
((function.hasOptionalPositionalParameters ||
function.hasNamedParameters) &&
function.numberOfParameters > argumentRegisters.length)
? function.numberOfParameters - argumentRegisters.length
: 0;
late final int _firstSpillSlotOffsetFromFP =
shadowParametersOffsetFromFP - _shadowParametersStackSlots * wordSize;
Arm64StackFrame(super.function);
@override
@@ -61,17 +73,36 @@ final class Arm64StackFrame extends StackFrame {
assert(isFinalized);
switch (location) {
case SpillSlot():
return firstSpillSlotOffsetFromFP - location.index * wordSize;
return _firstSpillSlotOffsetFromFP - location.index * wordSize;
case ParameterStackLocation():
final paramIndex = location.paramIndex;
final numParams = function.numberOfParameters;
assert(0 <= paramIndex && paramIndex < numParams);
return lastParameterOffsetFromFP +
(numParams - paramIndex - 1) * wordSize;
if (function.hasOptionalPositionalParameters ||
function.hasNamedParameters) {
return shadowParameterOffsetFromFP(paramIndex);
} else {
return lastParameterOffsetFromFP +
(numParams - paramIndex - 1) * wordSize;
}
}
}
@override
int get frameSizeToAllocate =>
roundUp((usedSpillSlots + maxArgumentsStackSlots) * wordSize, alignment);
int shadowParameterOffsetFromFP(int paramIndex) {
assert(
function.hasOptionalPositionalParameters || function.hasNamedParameters,
);
assert(paramIndex >= argumentRegisters.length);
assert(paramIndex < function.numberOfParameters);
return shadowParametersOffsetFromFP -
(paramIndex - argumentRegisters.length) * wordSize;
}
@override
int get frameSizeToAllocate => roundUp(
(_shadowParametersStackSlots + usedSpillSlots + maxArgumentsStackSlots) *
wordSize,
alignment,
);
}
@@ -146,8 +146,8 @@ final class LinearScanRegisterAllocator extends RegisterAllocator {
_instructionByPos[pos ~/ step] = block.id;
pos += step;
for (final instr in block) {
if (instr is Phi) {
// All Phis have the same position as their Block.
if (instr is Phi || instr is Parameter) {
// All Phis and Parameters have the same position as their Block.
_instructionPos[instr.id] = blockStartPos(block);
} else {
_instructionPos[instr.id] = pos;
@@ -42,6 +42,10 @@ abstract base class StackFrame {
/// Should be used only after the frame is finalized.
int offsetFromFP(StackLocation location);
/// Offset of the shadow parameter relative to the frame pointer, in bytes.
/// Should be used only after the frame is finalized.
int shadowParameterOffsetFromFP(int paramIndex);
/// Frame size to allocate, in bytes.
/// Should be used only after the frame is finalized.
int get frameSizeToAllocate;
@@ -7,6 +7,7 @@ export 'vm_offsets.g.dart';
const int smiBit = 0;
const int heapObjectTag = 1;
const int smiShift = 1;
const int barrierOverlapShift = 2;
int objectAlignment(int wordSize) => wordSize * 2;
@@ -191,9 +191,9 @@ B0 = EntryBlock()
v23 = Constant(8)
v24 = Constant(9)
v1 = Parameter(#functionTypeParameters) # RA: param[0] <- ()
ParallelMove output(param[0] -> vloc:R0)
ParallelMove output(param[0] -> vloc:R1)
v2 = Parameter(x) # RA: param[1] <- ()
ParallelMove output(param[1] -> vloc:R1)
ParallelMove output(param[1] -> vloc:R0)
v7 = DirectCall _GrowableList.(v5, v6) # 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]
ParallelMove output(R0 -> vloc:R0)
DirectCall print(v7) # 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]