| // 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 'dart:math' as math; |
| |
| import 'package:cfg/ir/constant_value.dart'; |
| import 'package:cfg/ir/field.dart'; |
| import 'package:cfg/ir/functions.dart'; |
| import 'package:cfg/ir/global_context.dart'; |
| import 'package:cfg/ir/instructions.dart'; |
| 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'; |
| import 'package:native_compiler/back_end/locations.dart'; |
| import 'package:native_compiler/back_end/object_pool.dart'; |
| import 'package:native_compiler/runtime/names.dart'; |
| import 'package:native_compiler/runtime/type_utils.dart'; |
| import 'package:native_compiler/runtime/vm_defs.dart'; |
| |
| final class Arm64CodeGenerator extends CodeGenerator { |
| final FunctionRegistry functionRegistry; |
| late final Arm64Assembler _asm; |
| |
| late final CFunction _asyncStarStreamControllerAdd = functionRegistry |
| .getFunction( |
| GlobalContext.instance.coreTypes.index.getProcedure( |
| 'dart:async', |
| '_AsyncStarStreamController', |
| 'add', |
| ), |
| ); |
| late final CFunction _asyncStarStreamControllerAddStream = functionRegistry |
| .getFunction( |
| GlobalContext.instance.coreTypes.index.getProcedure( |
| 'dart:async', |
| '_AsyncStarStreamController', |
| 'addStream', |
| ), |
| ); |
| |
| late final CField _syncStarIteratorCurrent = CField( |
| GlobalContext.instance.coreTypes.index.getField( |
| 'dart:async', |
| '_SyncStarIterator', |
| '_current', |
| ), |
| ); |
| |
| late final CField _syncStarIteratorYieldStarIterable = CField( |
| GlobalContext.instance.coreTypes.index.getField( |
| 'dart:async', |
| '_SyncStarIterator', |
| '_yieldStarIterable', |
| ), |
| ); |
| |
| Arm64CodeGenerator(super.backEndState, this.functionRegistry); |
| |
| @override |
| Assembler createAssembler() => _asm = Arm64Assembler( |
| backEndState.vmOffsets, |
| addCallSiteMetadata, |
| backEndState.objectLayout, |
| ); |
| |
| @override |
| void enterFrame() { |
| _asm.enterDartFrame(); |
| _asm.subImmediate( |
| stackPointerReg, |
| 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 number of arguments (without type arguments) as a Smi. |
| _asm.ldr( |
| argCountReg, |
| _asm.fieldAddress( |
| argumentsDescriptorReg, |
| vmOffsets.ArgumentsDescriptor_count_offset, |
| ), |
| ); |
| // Type arguments are passed as the first required positional parameter, |
| // but it is not counted in [ArgumentsDescriptor.count]. |
| final typeArg = function.hasFunctionTypeParameters ? 1 : 0; |
| // Arguments pointer points to FP + [ArgumentsDescriptor.count]*wordSize. |
| _asm.add( |
| argPtrReg, |
| FP, |
| ShiftedRegOperand(argCountReg, .LSL, log2wordSize - smiShift), |
| ); |
| // Offset of the first argument, relative to argPtrReg. |
| final int baseOffset = |
| Arm64StackFrame.lastParameterOffsetFromFP + (typeArg - 1) * wordSize; |
| // 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 - typeArg) << 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, baseOffset - (i + 1) * wordSize), |
| ); |
| if (i >= numRequired) { |
| _asm.b(labels[i + 1 - numRequired], .equal); |
| } else if (i + 1 >= numRequired) { |
| _asm.cmp(argCountReg, Immediate((i + 1 - typeArg) << smiShift)); |
| _asm.b(labels[i + 1 - numRequired], .equal); |
| } |
| } |
| for (; i < total; ++i) { |
| if (i >= numRequired) { |
| _asm.cmp(argCountReg, Immediate((i - typeArg) << smiShift)); |
| _asm.b(labels[i - numRequired], .equal); |
| } |
| final reg = (i < argumentRegisters.length) |
| ? argumentRegisters[i] |
| : tempReg; |
| _asm.ldr(reg, _asm.address(argPtrReg, baseOffset - i * 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 number of arguments (without type arguments) as a Smi. |
| _asm.ldr( |
| tempReg, |
| _asm.fieldAddress( |
| argumentsDescriptorReg, |
| vmOffsets.ArgumentsDescriptor_count_offset, |
| ), |
| ); |
| // Type arguments are passed as the first required positional parameter, |
| // but it is not counted in [ArgumentsDescriptor.count]. |
| final typeArg = function.hasFunctionTypeParameters ? 1 : 0; |
| // Arguments pointer points to FP + [ArgumentsDescriptor.count]*wordSize. |
| _asm.add( |
| argPtrReg, |
| FP, |
| ShiftedRegOperand(tempReg, .LSL, log2wordSize - smiShift), |
| ); |
| // Offset of the first argument, relative to argPtrReg. |
| final int baseOffset = |
| Arm64StackFrame.lastParameterOffsetFromFP + (typeArg - 1) * wordSize; |
| |
| 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, baseOffset - (i + 1) * wordSize), |
| ); |
| } |
| for (; i < numRequired; ++i) { |
| final reg = (i < argumentRegisters.length) |
| ? argumentRegisters[i] |
| : tempReg; |
| _asm.ldr(reg, _asm.address(argPtrReg, baseOffset - i * 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 - |
| heapObjectTag + |
| 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, baseOffset)); |
| if (proceed != null) { |
| _asm.bind(proceed); |
| } |
| if (i >= argumentRegisters.length) { |
| _asm.str( |
| destReg, |
| _asm.address(FP, stackFrame.shadowParameterOffsetFromFP(i)), |
| ); |
| } |
| } |
| } |
| |
| void _generateBranch( |
| Block trueSuccessor, |
| Block falseSuccessor, |
| void Function(bool, Label) branch, |
| ) { |
| if (canFallThroughTo(trueSuccessor)) { |
| branch(false, blockLabel(falseSuccessor)); |
| } else { |
| branch(true, blockLabel(trueSuccessor)); |
| if (!canFallThroughTo(falseSuccessor)) { |
| _asm.b(blockLabel(falseSuccessor)); |
| } |
| } |
| } |
| |
| @override |
| void visitBranch(Branch instr) { |
| final cond = inputReg(instr, 0); |
| _generateBranch(instr.trueSuccessor, instr.falseSuccessor, ( |
| bool value, |
| Label label, |
| ) { |
| _asm.branchIfBoolIs(cond, value, label); |
| }); |
| } |
| |
| @override |
| void visitCompareAndBranch(CompareAndBranch instr) { |
| final trueSuccessor = instr.trueSuccessor; |
| final falseSuccessor = instr.falseSuccessor; |
| final left = inputReg(instr, 0); |
| final right = instr.right; |
| switch (instr.op) { |
| case ComparisonOpcode.equal: |
| case ComparisonOpcode.intEqual: |
| case ComparisonOpcode.notEqual: |
| case ComparisonOpcode.intNotEqual: |
| if (right is Constant && right.value.isZero) { |
| _generateBranch(trueSuccessor, falseSuccessor, ( |
| bool value, |
| Label label, |
| ) { |
| if (value == |
| (instr.op == ComparisonOpcode.equal || |
| instr.op == ComparisonOpcode.intEqual)) { |
| _asm.cbz(left, label); |
| } else { |
| _asm.cbnz(left, label); |
| } |
| }); |
| return; |
| } |
| case ComparisonOpcode.intTestIsZero: |
| case ComparisonOpcode.intTestIsNotZero: |
| if (right is Constant && |
| right.value.isInt && |
| isPowerOf2(right.value.intValue)) { |
| final bitNumber = log2OfPowerOf2(right.value.intValue); |
| _generateBranch(trueSuccessor, falseSuccessor, ( |
| bool value, |
| Label label, |
| ) { |
| if (value == (instr.op == ComparisonOpcode.intTestIsZero)) { |
| _asm.tbz(left, bitNumber, label); |
| } else { |
| _asm.tbnz(left, bitNumber, label); |
| } |
| }); |
| } |
| default: |
| break; |
| } |
| switch (instr.op) { |
| case ComparisonOpcode.equal: |
| case ComparisonOpcode.notEqual: |
| case ComparisonOpcode.intEqual: |
| case ComparisonOpcode.intNotEqual: |
| case ComparisonOpcode.intLess: |
| case ComparisonOpcode.intLessOrEqual: |
| case ComparisonOpcode.intGreater: |
| case ComparisonOpcode.intGreaterOrEqual: |
| final (operand, negated) = _generateAddSubRightOperand(instr, right); |
| if (negated) { |
| _asm.cmn(left, operand); |
| } else { |
| _asm.cmp(left, operand); |
| } |
| break; |
| case ComparisonOpcode.intTestIsZero: |
| case ComparisonOpcode.intTestIsNotZero: |
| final operand = _generateLogicalRightOperand(instr, right); |
| _asm.tst(left, operand); |
| break; |
| default: |
| throw 'Unexpected ${instr.op}'; |
| } |
| _generateBranch(trueSuccessor, falseSuccessor, (bool value, Label label) { |
| final op = value ? instr.op : instr.op.negate(); |
| _asm.b(label, op.conditionCode); |
| }); |
| } |
| |
| (Operand, bool negated) _generateAddSubRightOperand( |
| Instruction instr, |
| Definition right, |
| ) { |
| if (right is Constant) { |
| if (right.value.isInt) { |
| final value = right.value.intValue; |
| if (value == 0) { |
| return (ZR, false); |
| } else if (_asm.canEncodeImm12(value)) { |
| return (Immediate(value), false); |
| } else if (_asm.canEncodeImm12(-value)) { |
| return (Immediate(-value), true); |
| } |
| } |
| _asm.loadConstant(tempReg, right.value); |
| return (tempReg, false); |
| } |
| return (inputReg(instr, 1), false); |
| } |
| |
| Operand _generateLogicalRightOperand(Instruction instr, Definition right) { |
| if (right is Constant) { |
| if (right.value.isInt) { |
| final value = right.value.intValue; |
| if (_asm.canEncodeBitMasks(value)) { |
| return Immediate(value); |
| } |
| } |
| _asm.loadConstant(tempReg, right.value); |
| return tempReg; |
| } |
| return inputReg(instr, 1); |
| } |
| |
| @override |
| void visitReturn(Return instr) { |
| assert(inputReg(instr, 0) == returnReg); |
| switch (graph.function.asyncMarker) { |
| case .Async: |
| _asm.jumpVmStub( |
| instr.value.type.canBeFuture |
| ? StubCode.ReturnAsync |
| : StubCode.ReturnAsyncNotFuture, |
| ); |
| return; |
| case .AsyncStar: |
| _asm.jumpVmStub(StubCode.ReturnAsyncStar); |
| return; |
| case .SyncStar: |
| // Overwrite the return value to indicate the end of iteration. |
| _asm.loadConstant(returnReg, ConstantValue.fromBool(false)); |
| break; |
| case .Sync: |
| break; |
| } |
| _asm.leaveDartFrame(); |
| _asm.ret(); |
| } |
| |
| @override |
| void visitUnreachable(Unreachable instr) { |
| _asm.unimplemented('Unreachable: ${instr.message}'); |
| } |
| |
| @override |
| void visitComparison(Comparison instr) { |
| final right = instr.right; |
| final result = outputReg(instr); |
| switch (instr.op) { |
| case .equal: |
| case .notEqual: |
| case .intEqual: |
| case .intNotEqual: |
| case .intLess: |
| case .intLessOrEqual: |
| case .intGreater: |
| case .intGreaterOrEqual: |
| final left = inputReg(instr, 0); |
| final (operand, negated) = _generateAddSubRightOperand(instr, right); |
| if (negated) { |
| _asm.cmn(left, operand); |
| } else { |
| _asm.cmp(left, operand); |
| } |
| break; |
| case .intTestIsZero: |
| case .intTestIsNotZero: |
| final left = inputReg(instr, 0); |
| final operand = _generateLogicalRightOperand(instr, right); |
| _asm.tst(left, operand); |
| break; |
| case .doubleEqual: |
| case .doubleNotEqual: |
| case .doubleLess: |
| case .doubleLessOrEqual: |
| case .doubleGreater: |
| case .doubleGreaterOrEqual: |
| final left = inputFPReg(instr, 0); |
| if (right is Constant && right.value.isZero) { |
| _asm.fcmp(left, Immediate(0)); |
| } else { |
| _asm.fcmp(left, inputFPReg(instr, 1)); |
| } |
| break; |
| case .identical: |
| case .notIdentical: |
| _asm.unimplemented( |
| 'Unimplemented: code generation for Comparison ${instr.op}', |
| ); |
| } |
| _asm.loadConstant(result, ConstantValue.fromBool(true)); |
| _asm.loadConstant(tempReg, ConstantValue.fromBool(false)); |
| _asm.csel(result, result, tempReg, instr.op.conditionCode); |
| } |
| |
| @override |
| void visitConstant(Constant instr) { |
| // No-op. |
| } |
| |
| void _passArguments(CallInstruction instr) { |
| Register pendingReg = invalidReg; |
| var offset = 0; |
| |
| Register getTempReg() => (pendingReg == tempReg) ? LR : tempReg; |
| |
| for (var i = instr.inputCount - 1; i >= 0; --i) { |
| final arg = instr.inputDefAt(i); |
| Register reg; |
| if (arg is Constant) { |
| if (arg.value.isZero) { |
| reg = ZR; |
| } else if (arg.value.isNull) { |
| reg = nullReg; |
| } else { |
| reg = getTempReg(); |
| _asm.loadConstant(reg, arg.value); |
| } |
| } else { |
| final loc = inputLoc(instr, i); |
| if (loc is Register) { |
| reg = loc; |
| } else { |
| reg = getTempReg(); |
| generateMove(loc, reg); |
| } |
| } |
| if (pendingReg == invalidReg) { |
| pendingReg = reg; |
| } else { |
| // TODO: support large offsets |
| _asm.stp(pendingReg, reg, RegOffsetAddress(stackPointerReg, offset)); |
| pendingReg = invalidReg; |
| offset += 2 * wordSize; |
| } |
| } |
| if (pendingReg != invalidReg) { |
| // TODO: support large offsets |
| _asm.str(pendingReg, RegOffsetAddress(stackPointerReg, offset)); |
| offset += wordSize; |
| } |
| assert(offset <= stackFrame.maxArgumentsStackSlots * wordSize); |
| } |
| |
| void _callFunction(CFunction function) { |
| // TODO: call directly through Code. |
| _asm.loadFromPool(functionReg, function); |
| _asm.ldr( |
| codeReg, |
| _asm.fieldAddress(functionReg, vmOffsets.Function_code_offset), |
| ); |
| _asm.ldr( |
| tempReg, |
| _asm.fieldAddress( |
| functionReg, |
| vmOffsets.Function_entry_point_offset.first, |
| ), |
| ); |
| _asm.blr(tempReg); |
| addCallSiteMetadata(); |
| } |
| |
| @override |
| void visitDirectCall(DirectCall instr) { |
| _passArguments(instr); |
| _asm.loadFromPool(argumentsDescriptorReg, instr.argumentsShape); |
| _callFunction(instr.target); |
| } |
| |
| @override |
| void visitInterfaceCall(InterfaceCall instr) { |
| _passArguments(instr); |
| _asm.loadFromPool(argumentsDescriptorReg, instr.argumentsShape); |
| // TODO: call through monomorphic/table dispatcher. |
| _asm.loadFromPool(R6, graph.function); |
| _asm.ldr( |
| R0, |
| _asm.address( |
| stackPointerReg, |
| (instr.inputCount - 1 - (instr.hasTypeArguments ? 1 : 0)) * wordSize, |
| ), |
| ); |
| _asm.loadPairFromPool( |
| inlineCacheDataReg, |
| codeReg, |
| InterfaceCallEntry( |
| graph.function, |
| instr.argumentsShape, |
| instr.interfaceTarget, |
| ), |
| ); |
| _asm.ldr( |
| tempReg, |
| _asm.fieldAddress(codeReg, vmOffsets.Code_entry_point_offset.first), |
| ); |
| _asm.blr(tempReg); |
| addCallSiteMetadata(); |
| } |
| |
| @override |
| void visitDynamicCall(DynamicCall instr) { |
| _passArguments(instr); |
| _asm.loadFromPool(argumentsDescriptorReg, instr.argumentsShape); |
| _asm.loadFromPool(R6, graph.function); |
| _asm.ldr( |
| R0, |
| _asm.address( |
| stackPointerReg, |
| (instr.inputCount - 1 - (instr.hasTypeArguments ? 1 : 0)) * wordSize, |
| ), |
| ); |
| _asm.loadPairFromPool( |
| inlineCacheDataReg, |
| codeReg, |
| DynamicCallEntry( |
| graph.function, |
| instr.argumentsShape, |
| instr.kind, |
| instr.selector, |
| ), |
| ); |
| _asm.ldr( |
| tempReg, |
| _asm.fieldAddress(codeReg, vmOffsets.Code_entry_point_offset.first), |
| ); |
| _asm.blr(tempReg); |
| addCallSiteMetadata(); |
| } |
| |
| @override |
| void visitClosureCall(ClosureCall instr) { |
| _passArguments(instr); |
| _asm.loadFromPool(argumentsDescriptorReg, instr.argumentsShape); |
| _asm.ldr( |
| R0, |
| _asm.address( |
| stackPointerReg, |
| (instr.inputCount - 1 - (instr.hasTypeArguments ? 1 : 0)) * wordSize, |
| ), |
| ); |
| _asm.ldr( |
| functionReg, |
| _asm.fieldAddress(R0, vmOffsets.Closure_function_offset), |
| ); |
| _asm.ldr( |
| codeReg, |
| _asm.fieldAddress(functionReg, vmOffsets.Function_code_offset), |
| ); |
| _asm.ldr( |
| tempReg, |
| _asm.fieldAddress( |
| functionReg, |
| vmOffsets.Function_entry_point_offset.first, |
| ), |
| ); |
| _asm.blr(tempReg); |
| addCallSiteMetadata(); |
| } |
| |
| @override |
| void visitParameter(Parameter instr) { |
| if (instr.isCatchParameter && |
| !instr.variable.isExceptionVariable && |
| !instr.variable.isStackTraceVariable) { |
| _asm.unimplemented('Unimplemented: code generation for catch Parameter'); |
| } |
| // No-op. |
| } |
| |
| @override |
| void visitLoadLocal(LoadLocal instr) => throw 'Unexpected LoadLocal'; |
| |
| @override |
| void visitStoreLocal(StoreLocal instr) => throw 'Unexpected StoreLocal'; |
| |
| @override |
| void visitLoadInstanceField(LoadInstanceField instr) { |
| final objectReg = inputReg(instr, 0); |
| final valueReg = outputReg(instr); |
| if (instr.checkInitialized) { |
| // TODO: initialized check for late fields. |
| _asm.unimplemented( |
| 'Unimplemented: code generation for LoadInstanceField.checkInitialized', |
| ); |
| return; |
| } |
| // TODO: unboxed fields |
| _asm.ldr( |
| valueReg, |
| _asm.fieldAddress(objectReg, objectLayout.getFieldOffset(instr.field)), |
| ); |
| } |
| |
| bool _canSkipWriteBarrier(Definition objectDef, Definition valueDef) => |
| (objectDef == valueDef) || |
| switch (valueDef) { |
| Constant(:var value) |
| when value.isNull || |
| value.isBool || |
| (value.isInt && objectLayout.isSmi(value.intValue)) => |
| true, |
| _ => false, |
| }; |
| |
| bool _canBeSmi(Definition def) => switch (def) { |
| Constant(:var value) => value.isInt && objectLayout.isSmi(value.intValue), |
| Definition(:var type) => type.canBeInt, |
| }; |
| |
| void _writeBarrier( |
| Register objectReg, |
| Register valueReg, |
| Register scratch1Reg, |
| Register scratch2Reg, { |
| required bool valueCanBeSmi, |
| }) { |
| // Test whether |
| // - object is old and not remembered and value is new, or |
| // - object is old and value is old and not marked and concurrent marking is in progress. |
| // If so, call the WriteBarrier stub. |
| |
| final done = Label(); |
| Label slowPath = addSlowPath(() { |
| _asm.callStub( |
| backEndState.stubFactory.getWriteBarrierStub(objectReg, valueReg), |
| ); |
| _asm.b(done); |
| }); |
| |
| if (valueCanBeSmi) { |
| _asm.tbz(valueReg, smiBit, done); |
| } else { |
| final ok = Label(); |
| _asm.tbnz(valueReg, smiBit, ok); |
| _asm.unimplemented('Smi value in _writeBarrier'); |
| _asm.bind(ok); |
| } |
| |
| _asm.ldr( |
| scratch1Reg, |
| _asm.address( |
| objectReg, |
| vmOffsets.Object_tags_offset - heapObjectTag, |
| .u8, |
| ), |
| .u8, |
| ); |
| _asm.ldr( |
| scratch2Reg, |
| _asm.address(valueReg, vmOffsets.Object_tags_offset - heapObjectTag, .u8), |
| .u8, |
| ); |
| _asm.and( |
| scratch1Reg, |
| scratch2Reg, |
| ShiftedRegOperand( |
| scratch1Reg, |
| .LSR, |
| vmOffsets.UntaggedObject_kBarrierOverlapShift, |
| ), |
| ); |
| _asm.tst(scratch1Reg, ShiftedRegOperand(heapBitsReg, .LSR, 32)); |
| _asm.b(slowPath, .notEqual); |
| |
| _asm.bind(done); |
| } |
| |
| @override |
| void visitStoreInstanceField(StoreInstanceField instr) { |
| final objectReg = inputReg(instr, 0); |
| final valueReg = inputReg(instr, 1); |
| final scratch1Reg = temporaryReg(instr, 0); |
| final scratch2Reg = temporaryReg(instr, 1); |
| if (instr.checkNotInitialized) { |
| // TODO: not-initialized check for late final fields. |
| _asm.unimplemented( |
| 'Unimplemented: code generation for StoreInstanceField.checkNotInitialized', |
| ); |
| return; |
| } |
| // TODO: unboxed fields |
| _asm.str( |
| valueReg, |
| _asm.fieldAddress(objectReg, objectLayout.getFieldOffset(instr.field)), |
| ); |
| if (!_canSkipWriteBarrier(instr.object, instr.value)) { |
| _writeBarrier( |
| objectReg, |
| valueReg, |
| scratch1Reg, |
| scratch2Reg, |
| valueCanBeSmi: _canBeSmi(instr.value), |
| ); |
| } |
| } |
| |
| void _loadStaticFieldAddress(Register dst, CField field, Register scratch) { |
| _asm.ldr( |
| scratch, |
| _asm.address(threadReg, vmOffsets.Thread_field_table_values_offset), |
| ); |
| _asm.loadFromPool(dst, StaticFieldOffset(field)); |
| _asm.add(dst, dst, scratch); |
| } |
| |
| @override |
| void visitLoadStaticField(LoadStaticField instr) { |
| final field = instr.field; |
| final valueReg = outputReg(instr); |
| final scratch1Reg = temporaryReg(instr, 0); |
| final scratch2Reg = temporaryReg(instr, 1); |
| |
| // TODO: shared static fields |
| _loadStaticFieldAddress(scratch1Reg, field, scratch2Reg); |
| _asm.ldr(valueReg, RegOffsetAddress(scratch1Reg, 0)); |
| |
| if (instr.checkInitialized) { |
| _asm.loadFromPool(scratch2Reg, SentinelConstant()); |
| _asm.cmp(valueReg, scratch2Reg); |
| |
| final done = Label(); |
| Label slowPath = addSlowPath(() { |
| if (hasNonTrivialInitializer(field.astField)) { |
| _callFunction( |
| functionRegistry.getFunction(field.astField, isInitializer: true), |
| ); |
| assert(valueReg == returnReg); |
| _loadStaticFieldAddress(scratch1Reg, field, scratch2Reg); |
| |
| if (field.isLate && field.isFinal) { |
| final ok = Label(); |
| _asm.ldr(scratch2Reg, RegOffsetAddress(scratch1Reg, 0)); |
| _asm.loadFromPool(tempReg, SentinelConstant()); |
| _asm.cmp(scratch2Reg, tempReg); |
| _asm.b(ok, .equal); |
| _asm.unimplemented( |
| 'Unimplemented: already initialized late final field in LoadStaticField', |
| ); |
| _asm.bind(ok); |
| } |
| |
| _asm.str(valueReg, RegOffsetAddress(scratch1Reg, 0)); |
| _asm.b(done); |
| } else { |
| _asm.unimplemented( |
| 'Unimplemented: uninitialized late field without initializer in LoadStaticField', |
| ); |
| } |
| }); |
| |
| _asm.b(slowPath, .equal); |
| _asm.bind(done); |
| } |
| } |
| |
| @override |
| void visitStoreStaticField(StoreStaticField instr) { |
| final field = instr.field; |
| final valueReg = inputReg(instr, 0); |
| final scratch1Reg = temporaryReg(instr, 0); |
| final scratch2Reg = temporaryReg(instr, 1); |
| |
| // TODO: shared static fields |
| _loadStaticFieldAddress(scratch1Reg, field, scratch2Reg); |
| |
| if (instr.checkNotInitialized) { |
| _asm.ldr(scratch2Reg, RegOffsetAddress(scratch1Reg, 0)); |
| _asm.loadFromPool(tempReg, SentinelConstant()); |
| _asm.cmp(scratch2Reg, tempReg); |
| |
| final done = Label(); |
| Label slowPath = addSlowPath(() { |
| _asm.unimplemented( |
| 'Unimplemented: already initialized late final field in StoreStaticField', |
| ); |
| _asm.b(done); |
| }); |
| |
| _asm.b(slowPath, .notEqual); |
| _asm.bind(done); |
| } |
| |
| _asm.str(valueReg, RegOffsetAddress(scratch1Reg, 0)); |
| } |
| |
| @override |
| void visitThrow(Throw instr) { |
| switch (instr.kind) { |
| case .exception: |
| assert(stackFrame.maxArgumentsStackSlots >= 2); |
| _asm.stp( |
| inputReg(instr, 0), |
| nullReg, // Space for result. |
| RegOffsetAddress(stackPointerReg, 0), |
| ); |
| _asm.callRuntime(RuntimeEntry.Throw, 1); |
| _asm.breakpoint(); |
| break; |
| case .rethrowException: |
| assert(stackFrame.maxArgumentsStackSlots >= 4); |
| _asm.stp(ZR, inputReg(instr, 1), RegOffsetAddress(stackPointerReg, 0)); |
| _asm.stp( |
| inputReg(instr, 0), |
| nullReg, // Space for result |
| RegOffsetAddress(stackPointerReg, 2 * wordSize), |
| ); |
| _asm.callRuntime(RuntimeEntry.ReThrow, 3); |
| _asm.breakpoint(); |
| break; |
| default: |
| _asm.unimplemented( |
| 'Unimplemented: code generation for Throw with ${instr.kind}', |
| ); |
| } |
| } |
| |
| @override |
| void visitNullCheck(NullCheck instr) { |
| final operandReg = inputReg(instr, 0); |
| final resultReg = outputReg(instr); |
| if (operandReg != resultReg) { |
| _asm.mov(resultReg, operandReg); |
| } |
| final Label slowPath = addSlowPath(() { |
| _asm.callRuntime(RuntimeEntry.NullCastError, 0); |
| _asm.breakpoint(); |
| }); |
| _asm.cmp(resultReg, nullReg); |
| _asm.b(slowPath, .equal); |
| } |
| |
| int _getNumberOfInputsForSubtypeTestCache( |
| ast.DartType type, { |
| required bool hasInstantiatorTypeArgs, |
| required bool hasFunctionTypeArgs, |
| }) { |
| if (type is ast.ExtensionType) { |
| type = type.extensionTypeErasure; |
| } |
| switch (type) { |
| case ast.NullType(): |
| case ast.NeverType(): |
| case ast.InterfaceType() when type.classNode.typeParameters.isEmpty: |
| return 1; |
| case ast.InterfaceType(): |
| case ast.FutureOrType(): |
| if (hasFunctionTypeArgs) { |
| return 4; |
| } |
| if (hasInstantiatorTypeArgs) { |
| return 3; |
| } |
| return 2; |
| case ast.FunctionType(): |
| case ast.RecordType(): |
| case ast.TypeParameterType(): |
| return 6; |
| case ast.ExtensionType(): |
| case ast.DynamicType(): |
| case ast.VoidType(): |
| case ast.StructuralParameterType(): |
| case ast.IntersectionType(): |
| case ast.TypedefType(): |
| case ast.InvalidType(): |
| case ast.AuxiliaryType(): |
| case ast.ExperimentalType(): |
| throw 'Unexpected type ${type.runtimeType} $type'; |
| } |
| } |
| |
| @override |
| void visitTypeCast(TypeCast instr) { |
| final operandReg = inputReg(instr, 0); |
| final resultReg = outputReg(instr); |
| if (operandReg != resultReg) { |
| _asm.mov(resultReg, operandReg); |
| } |
| |
| if (!instr.isChecked) { |
| return; |
| } |
| |
| final type = instr.testedType; |
| final dartType = type.dartType; |
| final done = Label(); |
| late final Label slowPath = addSlowPath(() { |
| assert(stackFrame.maxArgumentsStackSlots >= 3); |
| _asm.loadFromPool(tempReg, dartType); |
| _asm.stp(tempReg, resultReg, RegOffsetAddress(stackPointerReg, 0)); |
| _asm.str( |
| nullReg, // Space for result. |
| RegOffsetAddress(stackPointerReg, 2 * wordSize), |
| ); |
| _asm.callRuntime(RuntimeEntry.TypeError, 2); |
| _asm.breakpoint(); |
| }); |
| |
| // Handle a few built-in types, use TTS for other types. |
| switch (type) { |
| case ObjectType(): |
| _asm.cmp(resultReg, nullReg); |
| _asm.b(slowPath, .equal); |
| case NullType(): |
| _asm.cmp(resultReg, nullReg); |
| _asm.b(slowPath, .notEqual); |
| case IntType(): |
| _asm.tbz(resultReg, smiBit, done); |
| _asm.loadClassId(tempReg, resultReg); |
| _asm.cmpImmediate(tempReg, ClassId.MintCid.index); |
| _asm.b(slowPath, .notEqual); |
| case DoubleType(): |
| _asm.tbz(resultReg, smiBit, slowPath); |
| _asm.loadClassId(tempReg, resultReg); |
| _asm.cmpImmediate(tempReg, ClassId.DoubleCid.index); |
| _asm.b(slowPath, .notEqual); |
| case BoolType(): |
| _asm.tbz(resultReg, smiBit, slowPath); |
| _asm.loadClassId(tempReg, resultReg); |
| _asm.cmpImmediate(tempReg, ClassId.BoolCid.index); |
| _asm.b(slowPath, .notEqual); |
| case StringType(): |
| _asm.tbz(resultReg, smiBit, slowPath); |
| _asm.loadClassId(tempReg, resultReg); |
| _asm.cmpImmediate(tempReg, ClassId.OneByteStringCid.index); |
| _asm.b(done, .equal); |
| _asm.cmpImmediate(tempReg, ClassId.TwoByteStringCid.index); |
| _asm.b(slowPath, .notEqual); |
| default: |
| if (const IntType().isSubtypeOf(type)) { |
| _asm.tbz(resultReg, smiBit, done); |
| } else if (!type.canBeInt) { |
| _asm.tbz(resultReg, smiBit, slowPath); |
| } |
| if (type.isNullable) { |
| _asm.cmp(resultReg, nullReg); |
| _asm.b(done, .equal); |
| } |
| if (dartType is ast.TypeParameterType) { |
| final declaration = dartType.parameter.declaration; |
| assert(instr.inputCount == 3); |
| final instantiatorTypeArgsReg = inputReg(instr, 1); |
| final functionTypeArgsReg = inputReg(instr, 2); |
| final typeArgsReg = (declaration is ast.Class) |
| ? instantiatorTypeArgsReg |
| : functionTypeArgsReg; |
| final index = computeIndexOfTypeParameter(dartType.parameter); |
| _asm.cmp(typeArgsReg, nullReg); |
| _asm.b(done, .equal); |
| _asm.ldr( |
| TypeTestingStub.dstTypeReg, |
| _asm.fieldAddress( |
| typeArgsReg, |
| vmOffsets.TypeArguments_types_offset + |
| index * objectLayout.compressedWordSize, |
| ), |
| ); |
| } else { |
| _asm.loadFromPool(TypeTestingStub.dstTypeReg, dartType); |
| } |
| _asm.ldr( |
| TypeTestingStub.entryPointReg, |
| _asm.fieldAddress( |
| TypeTestingStub.dstTypeReg, |
| vmOffsets.AbstractType_type_test_stub_entry_point_offset, |
| ), |
| ); |
| bool isNullConstant(Definition def) => |
| def is Constant && def.value.isNull; |
| final hasInstantiatorTypeArgs = |
| instr.inputCount > 1 && !isNullConstant(instr.inputDefAt(1)); |
| final hasFunctionTypeArgs = |
| instr.inputCount > 1 && !isNullConstant(instr.inputDefAt(2)); |
| final stc = SubtypeTestCache( |
| _getNumberOfInputsForSubtypeTestCache( |
| dartType, |
| hasInstantiatorTypeArgs: hasInstantiatorTypeArgs, |
| hasFunctionTypeArgs: hasFunctionTypeArgs, |
| ), |
| ); |
| if (instr.inputCount == 1) { |
| _asm.mov(TypeTestingStub.instantiatorTypeArgumentsReg, nullReg); |
| _asm.mov(TypeTestingStub.functionTypeArgumentsReg, nullReg); |
| } |
| // VM expects exact code pattern for type testing stub calling sequence. |
| _asm.loadFromPool( |
| TypeTestingStub.subtypeTestCacheReg, |
| SubtypeTestCacheWithName(stc, Name('', null)), |
| ); |
| _asm.blr(TypeTestingStub.entryPointReg); |
| addCallSiteMetadata(); |
| } |
| |
| _asm.bind(done); |
| } |
| |
| @override |
| void visitTypeTest(TypeTest instr) { |
| final operandReg = inputReg(instr, 0); |
| final resultReg = outputReg(instr); |
| final doneFalse = Label(); |
| final doneTrue = Label(); |
| final done = Label(); |
| |
| // Handle a few built-in types, use STC for other types. |
| final type = instr.testedType; |
| switch (type) { |
| case ObjectType(): |
| _asm.cmp(operandReg, nullReg); |
| _asm.b(doneTrue, .notEqual); |
| case NullType(): |
| _asm.cmp(operandReg, nullReg); |
| _asm.b(doneTrue, .equal); |
| case IntType(): |
| _asm.tbz(operandReg, smiBit, doneTrue); |
| _asm.loadClassId(tempReg, operandReg); |
| _asm.cmpImmediate(tempReg, ClassId.MintCid.index); |
| _asm.b(doneTrue, .equal); |
| case DoubleType(): |
| _asm.tbz(operandReg, smiBit, doneFalse); |
| _asm.loadClassId(tempReg, operandReg); |
| _asm.cmpImmediate(tempReg, ClassId.DoubleCid.index); |
| _asm.b(doneTrue, .equal); |
| case BoolType(): |
| _asm.tbz(operandReg, smiBit, doneFalse); |
| _asm.loadClassId(tempReg, operandReg); |
| _asm.cmpImmediate(tempReg, ClassId.BoolCid.index); |
| _asm.b(doneTrue, .equal); |
| case StringType(): |
| _asm.tbz(operandReg, smiBit, doneFalse); |
| _asm.loadClassId(tempReg, operandReg); |
| _asm.cmpImmediate(tempReg, ClassId.OneByteStringCid.index); |
| _asm.b(doneTrue, .equal); |
| _asm.cmpImmediate(tempReg, ClassId.TwoByteStringCid.index); |
| _asm.b(doneTrue, .equal); |
| default: |
| if (const IntType().isSubtypeOf(type)) { |
| _asm.tbz(operandReg, smiBit, doneTrue); |
| } |
| if (type.isNullable) { |
| _asm.cmp(operandReg, nullReg); |
| _asm.b(doneTrue, .equal); |
| } |
| bool isNullConstant(Definition def) => |
| def is Constant && def.value.isNull; |
| final hasInstantiatorTypeArgs = |
| instr.inputCount > 1 && !isNullConstant(instr.inputDefAt(1)); |
| final hasFunctionTypeArgs = |
| instr.inputCount > 1 && !isNullConstant(instr.inputDefAt(2)); |
| final stc = SubtypeTestCache( |
| _getNumberOfInputsForSubtypeTestCache( |
| type.dartType, |
| hasInstantiatorTypeArgs: hasInstantiatorTypeArgs, |
| hasFunctionTypeArgs: hasFunctionTypeArgs, |
| ), |
| ); |
| final stub = switch (stc.numInputs) { |
| 1 => StubCode.Subtype1TestCache, |
| 2 => StubCode.Subtype2TestCache, |
| 3 => StubCode.Subtype3TestCache, |
| 4 => StubCode.Subtype4TestCache, |
| 6 => StubCode.Subtype6TestCache, |
| _ => |
| throw 'Unexpected number of SubtypeTestCache inputs ${stc.numInputs} (type $type)', |
| }; |
| |
| final Label slowPath = addSlowPath(() { |
| assert(stackFrame.maxArgumentsStackSlots >= 6); |
| _asm.loadFromPool(tempReg, type.dartType); |
| _asm.stp( |
| TypeTestingStub.subtypeTestCacheReg, |
| hasFunctionTypeArgs |
| ? TypeTestingStub.functionTypeArgumentsReg |
| : nullReg, |
| RegOffsetAddress(stackPointerReg, 0), |
| ); |
| _asm.stp( |
| hasInstantiatorTypeArgs |
| ? TypeTestingStub.instantiatorTypeArgumentsReg |
| : nullReg, |
| tempReg, |
| RegOffsetAddress(stackPointerReg, 2 * wordSize), |
| ); |
| _asm.stp( |
| TypeTestingStub.instanceReg, |
| nullReg, // Space for result |
| RegOffsetAddress(stackPointerReg, 4 * wordSize), |
| ); |
| _asm.callRuntime(RuntimeEntry.Instanceof, 5); |
| _asm.ldr(resultReg, RegOffsetAddress(stackPointerReg, 5 * wordSize)); |
| _asm.b(done); |
| }); |
| |
| _asm.loadFromPool(TypeTestingStub.subtypeTestCacheReg, stc); |
| _asm.callVmStub(stub); |
| _asm.cmp(TypeTestingStub.subtypeTestCacheResultReg, nullReg); |
| _asm.b(slowPath, .equal); |
| _asm.mov(resultReg, TypeTestingStub.subtypeTestCacheResultReg); |
| _asm.b(done); |
| } |
| |
| _asm.bind(doneFalse); |
| _asm.loadConstant(resultReg, ConstantValue.fromBool(false)); |
| _asm.b(done); |
| _asm.bind(doneTrue); |
| _asm.loadConstant(resultReg, ConstantValue.fromBool(true)); |
| _asm.bind(done); |
| } |
| |
| @override |
| void visitTypeArguments(TypeArguments instr) { |
| switch (computeTypeArgumentsReuse(instr.types)) { |
| case .instantiator: |
| _asm.mov(outputReg(instr), inputReg(instr, 0)); |
| return; |
| case .function: |
| _asm.mov(outputReg(instr), inputReg(instr, 1)); |
| return; |
| case .none: |
| break; |
| } |
| _asm.loadConstant( |
| InstantiateTypeArgumentsStub.uninstantiatedTypeArgumentsReg, |
| ConstantValue(TypeArgumentsConstant(instr.types)), |
| ); |
| _asm.callVmStub(StubCode.InstantiateTypeArguments); |
| } |
| |
| @override |
| void visitTypeLiteral(TypeLiteral instr) { |
| final instantiatorTypeArgsReg = inputReg(instr, 0); |
| final functionTypeArgsReg = inputReg(instr, 1); |
| final resultReg = outputReg(instr); |
| final type = instr.uninstantiatedType; |
| if (type is ast.TypeParameterType && |
| type.nullability != ast.Nullability.nullable) { |
| final declaration = type.parameter.declaration; |
| final index = computeIndexOfTypeParameter(type.parameter); |
| final typeArgsReg = (declaration is ast.Class) |
| ? instantiatorTypeArgsReg |
| : functionTypeArgsReg; |
| final done = Label(); |
| if (resultReg != typeArgsReg) { |
| _asm.mov(resultReg, nullReg); |
| } |
| _asm.cmp(typeArgsReg, nullReg); |
| _asm.b(done, .equal); |
| _asm.ldr( |
| resultReg, |
| _asm.fieldAddress( |
| typeArgsReg, |
| vmOffsets.TypeArguments_types_offset + |
| index * objectLayout.compressedWordSize, |
| ), |
| ); |
| _asm.bind(done); |
| return; |
| } |
| assert(stackFrame.maxArgumentsStackSlots >= 4); |
| _asm.loadFromPool(tempReg, type); |
| _asm.stp( |
| functionTypeArgsReg, |
| instantiatorTypeArgsReg, |
| RegOffsetAddress(stackPointerReg, 0), |
| ); |
| _asm.stp(tempReg, nullReg, RegOffsetAddress(stackPointerReg, 2 * wordSize)); |
| _asm.callRuntime(RuntimeEntry.InstantiateType, 3); |
| _asm.ldr(resultReg, RegOffsetAddress(stackPointerReg, 3 * wordSize)); |
| } |
| |
| @override |
| void visitAllocateObject(AllocateObject instr) { |
| final cls = (instr.type.dartType as ast.InterfaceType).classNode; |
| final instanceSize = objectLayout.getInstanceSize(cls); |
| final typeArgsField = objectLayout.getTypeArgumentsField(cls); |
| final typeArgumentsReg = AllocationStub.typeArgumentsReg; |
| final tagsReg = AllocationStub.tagsReg; |
| final resultReg = AllocationStub.resultReg; |
| assert(!instr.hasTypeArguments || inputReg(instr, 0) == typeArgumentsReg); |
| assert(outputReg(instr) == resultReg); |
| |
| // TODO: support huge objects |
| |
| final done = Label(); |
| Label slowPath = addSlowPath(() { |
| _asm.callStub(backEndState.stubFactory.getAllocationStub(cls)); |
| _asm.b(done); |
| }); |
| |
| _asm.loadFromPool(tagsReg, NewObjectTags(cls)); |
| _asm.inlineAllocation( |
| resultReg, |
| tagsReg, |
| AllocationStub.scratch1Reg, |
| AllocationStub.scratch2Reg, |
| instanceSize, |
| slowPath, |
| initializeFields: true, |
| ); |
| |
| if (typeArgsField != null) { |
| if (instr.hasTypeArguments) { |
| _asm.str( |
| typeArgumentsReg, |
| _asm.fieldAddress( |
| resultReg, |
| objectLayout.getFieldOffset(typeArgsField), |
| ), |
| ); |
| } else { |
| final typeArgs = getInstantiatorTypeArguments(cls, []); |
| if (typeArgs != null) { |
| _asm.loadConstant( |
| typeArgumentsReg, |
| ConstantValue(TypeArgumentsConstant(typeArgs)), |
| ); |
| _asm.str( |
| typeArgumentsReg, |
| _asm.fieldAddress( |
| resultReg, |
| objectLayout.getFieldOffset(typeArgsField), |
| ), |
| ); |
| } |
| } |
| } |
| |
| // TODO: allocation profile; allocation probe points. |
| |
| _asm.bind(done); |
| } |
| |
| @override |
| void visitAllocateClosure(AllocateClosure instr) { |
| final function = instr.function; |
| final closureLayout = instr.closureLayout; |
| final lengthAndFlags = vmOffsets.encodeClosureLengthAndFlags( |
| closureLayout.length, |
| hasDelayedTypeArgs: closureLayout.hasDelayedTypeArgs, |
| hasInstantiatorTypeArgs: closureLayout.hasClassTypeArgs, |
| hasFunctionTypeArgs: closureLayout.hasFunctionTypeArgs, |
| ); |
| final instanceSize = roundUp( |
| vmOffsets.Closure_elementsStartOffset + |
| closureLayout.length * objectLayout.compressedWordSize, |
| objectAlignment(wordSize), |
| ); |
| |
| final resultReg = AllocationStub.resultReg; |
| assert(outputReg(instr) == resultReg); |
| |
| final done = Label(); |
| Label slowPath = addSlowPath(() { |
| assert(stackFrame.maxArgumentsStackSlots >= 4); |
| _asm.loadImmediate(tempReg, lengthAndFlags << smiShift); |
| _asm.stp( |
| nullReg, // Context. |
| tempReg, |
| RegOffsetAddress(stackPointerReg, 0), |
| ); |
| _asm.loadFromPool(tempReg, function); |
| _asm.stp( |
| tempReg, |
| nullReg, // Space for result. |
| RegOffsetAddress(stackPointerReg, 2 * wordSize), |
| ); |
| _asm.callRuntime(RuntimeEntry.AllocateClosure, 3); |
| _asm.ldr(resultReg, RegOffsetAddress(stackPointerReg, 3 * wordSize)); |
| _asm.b(done); |
| }); |
| |
| _asm.loadImmediate( |
| AllocationStub.tagsReg, |
| vmOffsets.computeNewObjectTags( |
| ClassId.ClosureCid, |
| instanceSize, |
| log2wordSize, |
| ), |
| ); |
| _asm.inlineAllocation( |
| resultReg, |
| AllocationStub.tagsReg, |
| AllocationStub.scratch1Reg, |
| AllocationStub.scratch2Reg, |
| instanceSize, |
| slowPath, |
| initializeFields: true, |
| ); |
| final fieldReg = AllocationStub.scratch1Reg; |
| _asm.loadFromPool(fieldReg, function); |
| _asm.str( |
| fieldReg, |
| _asm.fieldAddress(resultReg, vmOffsets.Closure_function_offset), |
| ); |
| _asm.loadImmediate(fieldReg, lengthAndFlags << smiShift); |
| _asm.str( |
| fieldReg, |
| _asm.fieldAddress(resultReg, vmOffsets.Closure_length_and_flags_offset), |
| ); |
| _asm.str(ZR, _asm.fieldAddress(resultReg, vmOffsets.Closure_hash_offset)); |
| // TODO: initialize delayed type arguments. |
| _asm.bind(done); |
| } |
| |
| @override |
| void visitAllocateContext(AllocateContext instr) { |
| final instanceSize = roundUp( |
| vmOffsets.Context_elementsStartOffset + |
| instr.length * objectLayout.compressedWordSize, |
| objectAlignment(wordSize), |
| ); |
| final resultReg = AllocationStub.resultReg; |
| assert(outputReg(instr) == resultReg); |
| |
| final done = Label(); |
| Label slowPath = addSlowPath(() { |
| assert(stackFrame.maxArgumentsStackSlots >= 2); |
| _asm.loadImmediate(tempReg, instr.length << smiShift); |
| _asm.stp( |
| tempReg, |
| nullReg, // Space for result. |
| RegOffsetAddress(stackPointerReg, 0), |
| ); |
| _asm.callRuntime(RuntimeEntry.AllocateContext, 1); |
| _asm.ldr(resultReg, RegOffsetAddress(stackPointerReg, 1 * wordSize)); |
| _asm.b(done); |
| }); |
| |
| _asm.loadImmediate( |
| AllocationStub.tagsReg, |
| vmOffsets.computeNewObjectTags( |
| ClassId.ContextCid, |
| instanceSize, |
| log2wordSize, |
| ), |
| ); |
| _asm.inlineAllocation( |
| resultReg, |
| AllocationStub.tagsReg, |
| AllocationStub.scratch1Reg, |
| AllocationStub.scratch2Reg, |
| instanceSize, |
| slowPath, |
| initializeFields: true, |
| ); |
| final fieldReg = AllocationStub.scratch1Reg; |
| _asm.loadImmediate(fieldReg, instr.length); |
| _asm.str( |
| fieldReg, |
| _asm.fieldAddress(resultReg, vmOffsets.Context_num_variables_offset), |
| ); |
| _asm.bind(done); |
| } |
| |
| @override |
| void visitAllocateList(AllocateList instr) { |
| final tagsReg = temporaryReg(instr, 0); |
| final scratch1Reg = temporaryReg(instr, 1); |
| final scratch2Reg = temporaryReg(instr, 2); |
| final resultReg = outputReg(instr); |
| // TODO: support AllocateList with non-constant length |
| final length = (instr.length as Constant).value.intValue; |
| assert(objectLayout.isSmi(length)); |
| final instanceSize = roundUp( |
| vmOffsets.Array_data_offset + length * objectLayout.compressedWordSize, |
| objectAlignment(wordSize), |
| ); |
| assert(outputReg(instr) == resultReg); |
| |
| final done = Label(); |
| Label slowPath = addSlowPath(() { |
| assert(stackFrame.maxArgumentsStackSlots >= 3); |
| _asm.loadImmediate(tempReg, length << smiShift); |
| _asm.stp( |
| nullReg, // Type arguments. |
| tempReg, // Array length. |
| RegOffsetAddress(stackPointerReg, 0), |
| ); |
| _asm.str( |
| nullReg, // Space for result. |
| RegOffsetAddress(stackPointerReg, 2 * wordSize), |
| ); |
| _asm.callRuntime(RuntimeEntry.AllocateArray, 2); |
| _asm.ldr(resultReg, RegOffsetAddress(stackPointerReg, 2 * wordSize)); |
| _asm.b(done); |
| }); |
| |
| _asm.loadImmediate( |
| tagsReg, |
| vmOffsets.computeNewObjectTags( |
| ClassId.ArrayCid, |
| instanceSize, |
| log2wordSize, |
| ), |
| ); |
| _asm.inlineAllocation( |
| resultReg, |
| tagsReg, |
| scratch1Reg, |
| scratch2Reg, |
| instanceSize, |
| slowPath, |
| initializeFields: true, |
| ); |
| |
| _asm.loadImmediate(scratch1Reg, length << smiShift); |
| _asm.str( |
| scratch1Reg, |
| _asm.fieldAddress(resultReg, vmOffsets.Array_length_offset), |
| ); |
| _asm.bind(done); |
| } |
| |
| @override |
| void visitSetListElement(SetListElement instr) { |
| final listReg = inputReg(instr, 0); |
| final valueReg = inputReg(instr, 2); |
| final scratch1Reg = temporaryReg(instr, 0); |
| final scratch2Reg = temporaryReg(instr, 1); |
| // TODO: support SetListElement with non-constant index |
| final index = (instr.index as Constant).value.intValue; |
| _asm.str( |
| valueReg, |
| _asm.fieldAddress( |
| listReg, |
| vmOffsets.Array_data_offset + index * objectLayout.compressedWordSize, |
| ), |
| ); |
| if (!_canSkipWriteBarrier(instr.list, instr.value)) { |
| _writeBarrier( |
| listReg, |
| valueReg, |
| scratch1Reg, |
| scratch2Reg, |
| valueCanBeSmi: _canBeSmi(instr.value), |
| ); |
| } |
| } |
| |
| @override |
| void visitAllocateRecord(AllocateRecord instr) { |
| final instanceSize = roundUp( |
| vmOffsets.Record_elementsStartOffset + |
| instr.type.numFields * objectLayout.compressedWordSize, |
| objectAlignment(wordSize), |
| ); |
| final resultReg = AllocationStub.resultReg; |
| assert(outputReg(instr) == resultReg); |
| |
| final done = Label(); |
| Label slowPath = addSlowPath(() { |
| assert(stackFrame.maxArgumentsStackSlots >= 2); |
| _asm.loadFromPool(tempReg, instr.type.shape); |
| _asm.stp( |
| tempReg, // Record shape. |
| nullReg, // Space for result. |
| RegOffsetAddress(stackPointerReg, 0), |
| ); |
| _asm.callRuntime(RuntimeEntry.AllocateRecord, 1); |
| _asm.ldr(resultReg, RegOffsetAddress(stackPointerReg, 1 * wordSize)); |
| _asm.b(done); |
| }); |
| |
| _asm.loadImmediate( |
| AllocationStub.tagsReg, |
| vmOffsets.computeNewObjectTags( |
| ClassId.RecordCid, |
| instanceSize, |
| log2wordSize, |
| ), |
| ); |
| _asm.inlineAllocation( |
| resultReg, |
| AllocationStub.tagsReg, |
| AllocationStub.scratch1Reg, |
| AllocationStub.scratch2Reg, |
| instanceSize, |
| slowPath, |
| initializeFields: true, |
| ); |
| final fieldReg = AllocationStub.scratch1Reg; |
| _asm.loadFromPool(fieldReg, instr.type.shape); |
| _asm.str( |
| fieldReg, |
| _asm.fieldAddress(resultReg, vmOffsets.Record_shape_offset), |
| ); |
| _asm.bind(done); |
| } |
| |
| @override |
| void visitBoxInt(BoxInt instr) { |
| var 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 instanceSize = vmOffsets.Mint_InstanceSize; |
| |
| Label slowPath = addSlowPath(() { |
| _asm.unimplemented('Unimplemented: code generation for BoxInt slow path'); |
| _asm.b(done); |
| }); |
| |
| if (operandReg == resultReg) { |
| _asm.mov(tempReg, operandReg); |
| operandReg = tempReg; |
| } |
| |
| _asm.adds(resultReg, operandReg, operandReg); |
| _asm.b(done, .noOverflow); |
| |
| _asm.loadImmediate( |
| tagsReg, |
| vmOffsets.computeNewObjectTags( |
| ClassId.MintCid, |
| instanceSize, |
| log2wordSize, |
| ), |
| ); |
| _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 instanceSize = vmOffsets.Double_InstanceSize; |
| |
| Label slowPath = addSlowPath(() { |
| _asm.unimplemented( |
| 'Unimplemented: code generation for BoxDouble slow path', |
| ); |
| _asm.b(done); |
| }); |
| |
| _asm.loadImmediate( |
| tagsReg, |
| vmOffsets.computeNewObjectTags( |
| ClassId.DoubleCid, |
| instanceSize, |
| log2wordSize, |
| ), |
| ); |
| _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) { |
| final leftReg = inputReg(instr, 0); |
| final right = instr.right; |
| final resultReg = outputReg(instr); |
| switch (instr.op) { |
| case .add: |
| case .sub: |
| final (rightOperand, negated) = _generateAddSubRightOperand( |
| instr, |
| right, |
| ); |
| if ((instr.op == .sub) == negated) { |
| _asm.add(resultReg, leftReg, rightOperand); |
| } else { |
| _asm.sub(resultReg, leftReg, rightOperand); |
| } |
| break; |
| case .mul: |
| Register rightReg; |
| if (right is Constant) { |
| rightReg = tempReg; |
| _asm.loadConstant(rightReg, right.value); |
| } else { |
| rightReg = inputReg(instr, 1); |
| } |
| _asm.mul(resultReg, leftReg, rightReg); |
| break; |
| case .truncatingDiv: |
| case .mod: |
| case .rem: |
| _asm.unimplemented( |
| 'Unimplemented: code generation for BinaryIntOp ${instr.op.token}', |
| ); |
| break; |
| case .bitOr: |
| case .bitAnd: |
| case .bitXor: |
| final rightOperand = _generateLogicalRightOperand(instr, right); |
| switch (instr.op) { |
| case .bitOr: |
| _asm.orr(resultReg, leftReg, rightOperand); |
| break; |
| case .bitAnd: |
| _asm.and(resultReg, leftReg, rightOperand); |
| break; |
| case .bitXor: |
| _asm.eor(resultReg, leftReg, rightOperand); |
| break; |
| default: |
| throw "Unexpected logical op ${instr.op}"; |
| } |
| break; |
| case .shiftLeft: |
| case .shiftRight: |
| case .unsignedShiftRight: |
| final done = Label(); |
| late final Label slowPath = addSlowPath(() { |
| _asm.unimplemented( |
| 'Unimplemented: code generation for slow path of BinaryIntOp ${instr.op.token}', |
| ); |
| _asm.b(done); |
| }); |
| if (right is Constant) { |
| final shift = right.value.intValue; |
| if (shift < 0) { |
| _asm.b(slowPath); |
| } else if (shift > 0 && shift < 64) { |
| switch (instr.op) { |
| case .shiftLeft: |
| _asm.lsl(resultReg, leftReg, shift); |
| break; |
| case .shiftRight: |
| _asm.asr(resultReg, leftReg, shift); |
| break; |
| case .unsignedShiftRight: |
| _asm.lsr(resultReg, leftReg, shift); |
| break; |
| default: |
| throw "Unexpected shift op ${instr.op}"; |
| } |
| } else { |
| // Guaranteed by simplification pass. |
| throw 'Unexpected shift amount $shift'; |
| } |
| } else { |
| final rightReg = inputReg(instr, 1); |
| _asm.cmp(rightReg, Immediate(63)); |
| _asm.b(slowPath, .unsignedGreater); |
| switch (instr.op) { |
| case .shiftLeft: |
| _asm.lslv(resultReg, leftReg, rightReg); |
| break; |
| case .shiftRight: |
| _asm.asrv(resultReg, leftReg, rightReg); |
| break; |
| case .unsignedShiftRight: |
| _asm.lsrv(resultReg, leftReg, rightReg); |
| break; |
| default: |
| throw "Unexpected shift op ${instr.op}"; |
| } |
| } |
| _asm.bind(done); |
| break; |
| } |
| } |
| |
| @override |
| void visitUnaryIntOp(UnaryIntOp instr) { |
| final operandReg = inputReg(instr, 0); |
| switch (instr.op) { |
| case .neg: |
| _asm.neg(outputReg(instr), operandReg); |
| break; |
| case .bitNot: |
| _asm.mvn(outputReg(instr), operandReg); |
| break; |
| case .toDouble: |
| _asm.scvtf(outputFPReg(instr), operandReg); |
| default: |
| _asm.unimplemented( |
| 'Unimplemented: code generation for UnaryIntOp ${instr.op.token}', |
| ); |
| } |
| } |
| |
| @override |
| void visitBinaryDoubleOp(BinaryDoubleOp instr) { |
| final leftReg = inputFPReg(instr, 0); |
| final rightReg = inputFPReg(instr, 1); |
| switch (instr.op) { |
| case .add: |
| _asm.fadd(outputFPReg(instr), leftReg, rightReg); |
| case .sub: |
| _asm.fsub(outputFPReg(instr), leftReg, rightReg); |
| case .mul: |
| _asm.fmul(outputFPReg(instr), leftReg, rightReg); |
| case .div: |
| _asm.fdiv(outputFPReg(instr), leftReg, rightReg); |
| default: |
| _asm.unimplemented( |
| 'Unimplemented: code generation for BinaryDoubleOp ${instr.op.token}', |
| ); |
| } |
| } |
| |
| @override |
| void visitUnaryDoubleOp(UnaryDoubleOp instr) { |
| _asm.unimplemented( |
| 'Unimplemented: code generation for UnaryDoubleOp ${instr.op.token}', |
| ); |
| } |
| |
| @override |
| void visitUnaryBoolOp(UnaryBoolOp instr) { |
| final operandReg = inputReg(instr, 0); |
| final resultReg = outputReg(instr); |
| switch (instr.op) { |
| case .not: |
| final boolValueBit = boolValueBitPosition(log2wordSize); |
| _asm.eor(resultReg, operandReg, Immediate(1 << boolValueBit)); |
| break; |
| } |
| } |
| |
| @override |
| void visitEnterSuspendableFunction(EnterSuspendableFunction instr) { |
| final asyncMarker = graph.function.asyncMarker; |
| final stub = switch (asyncMarker) { |
| .Async => StubCode.InitAsync, |
| .AsyncStar => StubCode.InitAsyncStar, |
| .SyncStar => StubCode.InitSyncStar, |
| .Sync => throw 'Unexpected async marker', |
| }; |
| _asm.callVmStub(stub); |
| // Suspend async* and sync* functions at the beginning. |
| if (asyncMarker == .AsyncStar) { |
| _asm.mov(SuspendStub.argumentReg, nullReg); |
| _asm.callVmStub(StubCode.YieldAsyncStar); |
| } else if (asyncMarker == .SyncStar) { |
| _asm.mov(SuspendStub.argumentReg, nullReg); |
| _asm.callVmStub(StubCode.SuspendSyncStarAtStart); |
| } |
| } |
| |
| @override |
| void visitSuspend(Suspend instr) { |
| void loadFunctionData(Register dst) { |
| _asm.ldr(tempReg, _asm.address(FP, stackFrame.suspendStateOffsetFromFP)); |
| _asm.ldr( |
| dst, |
| _asm.fieldAddress(tempReg, vmOffsets.SuspendState_function_data_offset), |
| ); |
| } |
| |
| switch (instr.op) { |
| case .await: |
| _asm.callVmStub(StubCode.Await); |
| break; |
| case .awaitWithTypeCheck: |
| _asm.callVmStub(StubCode.AwaitWithTypeCheck); |
| break; |
| case .asyncYield || .asyncYieldStar: |
| // Load controller from suspend state. |
| loadFunctionData(tempReg); |
| // Call controller.add or addStream. |
| assert(stackFrame.maxArgumentsStackSlots >= 2); |
| _asm.stp( |
| SuspendStub.argumentReg, |
| tempReg, |
| RegOffsetAddress(stackPointerReg, 0), |
| ); |
| _callFunction( |
| instr.op == .asyncYield |
| ? _asyncStarStreamControllerAdd |
| : _asyncStarStreamControllerAddStream, |
| ); |
| // It returns true if subscription was canceled. |
| final done = Label(); |
| _asm.branchIfBoolIs(returnReg, true, done); |
| // Suspend. |
| _asm.mov(SuspendStub.argumentReg, nullReg); |
| _asm.callVmStub(StubCode.YieldAsyncStar); |
| _asm.bind(done); |
| break; |
| case .syncYield || .syncYieldStar: |
| // Load iterator from suspend state. |
| final iteratorReg = temporaryReg(instr, 0); |
| final scratch1Reg = temporaryReg(instr, 1); |
| final scratch2Reg = temporaryReg(instr, 2); |
| loadFunctionData(iteratorReg); |
| // Set _SyncStarIterator._current or _yieldStarIterable. |
| _asm.str( |
| SuspendStub.argumentReg, |
| _asm.fieldAddress( |
| iteratorReg, |
| objectLayout.getFieldOffset( |
| instr.op == .syncYield |
| ? _syncStarIteratorCurrent |
| : _syncStarIteratorYieldStarIterable, |
| ), |
| ), |
| ); |
| _writeBarrier( |
| iteratorReg, |
| SuspendStub.argumentReg, |
| scratch1Reg, |
| scratch2Reg, |
| valueCanBeSmi: _canBeSmi(instr.operand), |
| ); |
| // Suspend. |
| _asm.mov(SuspendStub.argumentReg, nullReg); |
| _asm.callVmStub(StubCode.SuspendSyncStarAtYield); |
| break; |
| } |
| } |
| |
| @override |
| Location getMoveTempRegister(RegisterClass registerClass) => |
| switch (registerClass) { |
| .cpu => tempReg, |
| .fpu => fpTempReg, |
| }; |
| |
| @override |
| void generateMove(Location from, Location to) { |
| switch (from) { |
| case Register(): |
| switch (to) { |
| case Register(): |
| _asm.mov(to, from); |
| return; |
| case StackLocation(): |
| _asm.str(from, _asm.address(FP, stackFrame.offsetFromFP(to))); |
| return; |
| default: |
| break; |
| } |
| case StackLocation(): |
| switch (to) { |
| case Register(): |
| _asm.ldr(to, _asm.address(FP, stackFrame.offsetFromFP(from))); |
| return; |
| case FPRegister(): |
| _asm.fldr(to, _asm.address(FP, stackFrame.offsetFromFP(from))); |
| return; |
| default: |
| break; |
| } |
| case FPRegister(): |
| switch (to) { |
| case StackLocation(): |
| _asm.fstr(from, _asm.address(FP, stackFrame.offsetFromFP(to))); |
| return; |
| default: |
| break; |
| } |
| default: |
| break; |
| } |
| _asm.unimplemented( |
| 'Unimplemented: code generation for generateMove ${from.runtimeType} -> ${to.runtimeType}', |
| ); |
| } |
| |
| @override |
| void generateLoadConstant(ConstantValue value, Location to) { |
| switch (to) { |
| case Register(): |
| _asm.loadConstant(to, value); |
| return; |
| case FPRegister(): |
| assert(value.isDouble && value.isUnboxed); |
| _asm.loadDoubleImmediate(to, value.doubleValue); |
| return; |
| case StackLocation(): |
| _asm.loadConstant(tempReg, value); |
| _asm.str(tempReg, _asm.address(FP, stackFrame.offsetFromFP(to))); |
| return; |
| } |
| } |
| } |
| |
| extension on ComparisonOpcode { |
| Condition get conditionCode => switch (this) { |
| .equal => Condition.equal, |
| .notEqual => Condition.notEqual, |
| .identical => Condition.equal, |
| .notIdentical => Condition.notEqual, |
| .intEqual => Condition.equal, |
| .intNotEqual => Condition.notEqual, |
| .intLess => Condition.less, |
| .intLessOrEqual => Condition.lessOrEqual, |
| .intGreater => Condition.greater, |
| .intGreaterOrEqual => Condition.greaterOrEqual, |
| .intTestIsZero => Condition.equal, |
| .intTestIsNotZero => Condition.notEqual, |
| .doubleEqual => Condition.equal, |
| .doubleNotEqual => Condition.notEqual, |
| .doubleLess => Condition.unsignedLess, // LO |
| .doubleLessOrEqual => Condition.unsignedLessOrEqual, // LS |
| .doubleGreater => Condition.greater, // GT |
| .doubleGreaterOrEqual => Condition.greaterOrEqual, // GE |
| }; |
| } |