/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * * Copyright 2015 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
static uint32_t ResultStackSize(ValType type) { switch (type.kind()) { case ValType::I32: return ABIResult::StackSizeOfInt32; case ValType::I64: return ABIResult::StackSizeOfInt64; case ValType::F32: return ABIResult::StackSizeOfFloat; case ValType::F64: return ABIResult::StackSizeOfDouble; #ifdef ENABLE_WASM_SIMD case ValType::V128: return ABIResult::StackSizeOfV128; #endif case ValType::Ref: return ABIResult::StackSizeOfPtr; default:
MOZ_CRASH("Unexpected result type");
}
}
// Compute the size of the stack slot that the wasm ABI requires be allocated // for a particular MIRType. Note that this sometimes differs from the // MIRType's natural size. See also ResultStackSize above and ABIResult::size() // and ABIResultIter below.
uint32_t js::wasm::MIRTypeToABIResultSize(jit::MIRType type) { switch (type) { case MIRType::Int32: return ABIResult::StackSizeOfInt32; case MIRType::Int64: return ABIResult::StackSizeOfInt64; case MIRType::Float32: return ABIResult::StackSizeOfFloat; case MIRType::Double: return ABIResult::StackSizeOfDouble; #ifdef ENABLE_WASM_SIMD case MIRType::Simd128: return ABIResult::StackSizeOfV128; #endif case MIRType::Pointer: case MIRType::WasmAnyRef: return ABIResult::StackSizeOfPtr; default:
MOZ_CRASH("MIRTypeToABIResultSize - unhandled case");
}
}
if (taken) {
regs.take(taken.value());
} Register temp = regs.takeAnyGeneral();
{
MOZ_ASSERT(MaybeGetJitContext(), "codegen debug checks require a jit context"); # ifdef JS_CODEGEN_ARM64 if (IsCompilingWasm()) {
masm.setupWasmABICall();
} else { // JS ARM64 has an extra stack pointer which is not managed in WASM.
masm.setupUnalignedABICall(temp);
} # else
masm.setupUnalignedABICall(temp); # endif
passArgAndCall(IsCompilingWasm(), temp);
}
GenPrint(channel, masm, Nothing(), [&](bool inWasm, Register temp) { // If we've gone this far, it means we're actually using the debugging // strings. In this case, we leak them! This is only for debugging, and // doing the right thing is cumbersome (in Ion, it'd mean add a vec of // strings to the IonScript; in wasm, it'd mean add it to the current // Module and serialize it properly). constchar* text = str.release();
staticbool FinishOffsets(MacroAssembler& masm, Offsets* offsets) { // On old ARM hardware, constant pools could be inserted and they need to // be flushed before considering the size of the masm.
masm.flushBuffer();
offsets->end = masm.size(); return !masm.oom();
}
staticvoid SetupABIArguments(MacroAssembler& masm, const FuncExport& fe, const FuncType& funcType, Register argv, Register scratch) { // Copy parameters out of argv and into the registers/stack-slots specified by // the wasm ABI. // // SetupABIArguments are only used for C++ -> wasm calls through callExport(), // and V128 and Ref types (other than externref) are not currently allowed.
ArgTypeVector args(funcType); for (WasmABIArgIter iter(args); !iter.done(); iter++) { unsigned argOffset = iter.index() * sizeof(ExportArg);
Address src(argv, argOffset);
MIRType type = iter.mirType(); switch (iter->kind()) { case ABIArg::GPR: if (type == MIRType::Int32) {
masm.load32(src, iter->gpr());
} elseif (type == MIRType::Int64) {
masm.load64(src, iter->gpr64());
} elseif (type == MIRType::WasmAnyRef) {
masm.loadPtr(src, iter->gpr());
} elseif (type == MIRType::StackResults) {
MOZ_ASSERT(args.isSyntheticStackResultPointerArg(iter.index()));
masm.loadPtr(src, iter->gpr());
} else {
MOZ_CRASH("unknown GPR type");
} break; #ifdef JS_CODEGEN_REGISTER_PAIR case ABIArg::GPR_PAIR: if (type == MIRType::Int64) {
masm.load64(src, iter->gpr64());
} else {
MOZ_CRASH("wasm uses hardfp for function calls.");
} break; #endif case ABIArg::FPU: {
static_assert(sizeof(ExportArg) >= jit::Simd128DataSize, "ExportArg must be big enough to store SIMD values"); switch (type) { case MIRType::Double:
masm.loadDouble(src, iter->fpu()); break; case MIRType::Float32:
masm.loadFloat32(src, iter->fpu()); break; case MIRType::Simd128: #ifdef ENABLE_WASM_SIMD // This is only used by the testing invoke path, // wasmLosslessInvoke, and is guarded against in normal JS-API // call paths.
masm.loadUnalignedSimd128(src, iter->fpu()); break; #else
MOZ_CRASH("V128 not supported in SetupABIArguments"); #endif default:
MOZ_CRASH("unexpected FPU type"); break;
} break;
} case ABIArg::Stack: switch (type) { case MIRType::Int32:
masm.load32(src, scratch);
masm.storePtr(scratch, Address(masm.getStackPointer(),
iter->offsetFromArgBase())); break; case MIRType::Int64: {
RegisterOrSP sp = masm.getStackPointer();
masm.copy64(src, Address(sp, iter->offsetFromArgBase()), scratch); break;
} case MIRType::WasmAnyRef:
masm.loadPtr(src, scratch);
masm.storePtr(scratch, Address(masm.getStackPointer(),
iter->offsetFromArgBase())); break; case MIRType::Double: {
ScratchDoubleScope fpscratch(masm);
masm.loadDouble(src, fpscratch);
masm.storeDouble(fpscratch, Address(masm.getStackPointer(),
iter->offsetFromArgBase())); break;
} case MIRType::Float32: {
ScratchFloat32Scope fpscratch(masm);
masm.loadFloat32(src, fpscratch);
masm.storeFloat32(fpscratch, Address(masm.getStackPointer(),
iter->offsetFromArgBase())); break;
} case MIRType::Simd128: { #ifdef ENABLE_WASM_SIMD // This is only used by the testing invoke path, // wasmLosslessInvoke, and is guarded against in normal JS-API // call paths.
ScratchSimd128Scope fpscratch(masm);
masm.loadUnalignedSimd128(src, fpscratch);
masm.storeUnalignedSimd128(
fpscratch,
Address(masm.getStackPointer(), iter->offsetFromArgBase())); break; #else
MOZ_CRASH("V128 not supported in SetupABIArguments"); #endif
} case MIRType::StackResults: {
MOZ_ASSERT(args.isSyntheticStackResultPointerArg(iter.index()));
masm.loadPtr(src, scratch);
masm.storePtr(scratch, Address(masm.getStackPointer(),
iter->offsetFromArgBase())); break;
} default:
MOZ_CRASH("unexpected stack arg type");
} break; case ABIArg::Uninitialized:
MOZ_CRASH("Uninitialized ABIArg kind");
}
}
}
staticvoid StoreRegisterResult(MacroAssembler& masm, const FuncExport& fe, const FuncType& funcType, Register loc) {
ResultType results = ResultType::Vector(funcType.results());
DebugOnly<bool> sawRegisterResult = false; for (ABIResultIter iter(results); !iter.done(); iter.next()) { const ABIResult& result = iter.cur(); if (result.inRegister()) {
MOZ_ASSERT(!sawRegisterResult);
sawRegisterResult = true; switch (result.type().kind()) { case ValType::I32:
masm.store32(result.gpr(), Address(loc, 0)); break; case ValType::I64:
masm.store64(result.gpr64(), Address(loc, 0)); break; case ValType::V128: #ifdef ENABLE_WASM_SIMD
masm.storeUnalignedSimd128(result.fpr(), Address(loc, 0)); break; #else
MOZ_CRASH("V128 not supported in StoreABIReturn"); #endif case ValType::F32:
masm.storeFloat32(result.fpr(), Address(loc, 0)); break; case ValType::F64:
masm.storeDouble(result.fpr(), Address(loc, 0)); break; case ValType::Ref:
masm.storePtr(result.gpr(), Address(loc, 0)); break;
}
}
}
MOZ_ASSERT(sawRegisterResult == (results.length() > 0));
}
#ifdefined(JS_CODEGEN_ARM) // The ARM system ABI also includes d15 & s31 in the non volatile float // registers. Also exclude lr (a.k.a. r14) as we preserve it manually. staticconst LiveRegisterSet NonVolatileRegs = LiveRegisterSet(
GeneralRegisterSet(Registers::NonVolatileMask &
~(Registers::SetType(1) << Registers::lr)),
FloatRegisterSet(FloatRegisters::NonVolatileMask |
(FloatRegisters::SetType(1) << FloatRegisters::d15) |
(FloatRegisters::SetType(1) << FloatRegisters::s31))); #elifdefined(JS_CODEGEN_ARM64) // Exclude the Link Register (x30) because it is preserved manually. // // Include x16 (scratch) to make a 16-byte aligned amount of integer registers. // Include d31 (scratch) to make a 16-byte aligned amount of floating registers. staticconst LiveRegisterSet NonVolatileRegs = LiveRegisterSet(
GeneralRegisterSet((Registers::NonVolatileMask &
~(Registers::SetType(1) << Registers::lr)) |
(Registers::SetType(1) << Registers::x16)),
FloatRegisterSet(FloatRegisters::NonVolatileMask |
FloatRegisters::NonAllocatableMask)); #else staticconst LiveRegisterSet NonVolatileRegs =
LiveRegisterSet(GeneralRegisterSet(Registers::NonVolatileMask),
FloatRegisterSet(FloatRegisters::NonVolatileMask)); #endif
staticvoid AssertExpectedSP(MacroAssembler& masm) { #ifdef JS_CODEGEN_ARM64
MOZ_ASSERT(sp.Is(masm.GetStackPointer64())); # ifdef DEBUG // Since we're asserting that SP is the currently active stack pointer, // let's also in effect assert that PSP is dead -- by setting it to 1, so as // to cause to cause any attempts to use it to segfault in an easily // identifiable way.
masm.asVIXL().Mov(PseudoStackPointer64, 1); # endif #endif
}
template <class Operand> staticvoid WasmPush(MacroAssembler& masm, const Operand& op) { #ifdef JS_CODEGEN_ARM64 // Allocate a pad word so that SP can remain properly aligned. |op| will be // written at the lower-addressed of the two words pushed here.
masm.reserveStack(WasmPushSize);
masm.storePtr(op, Address(masm.getStackPointer(), 0)); #else
masm.Push(op); #endif
}
staticvoid WasmPop(MacroAssembler& masm, Register r) { #ifdef JS_CODEGEN_ARM64 // Also pop the pad word allocated by WasmPush.
masm.loadPtr(Address(masm.getStackPointer(), 0), r);
masm.freeStack(WasmPushSize); #else
masm.Pop(r); #endif
}
// Generate a stub that enters wasm from a C++ caller via the native ABI. The // signature of the entry point is Module::ExportFuncPtr. The exported wasm // function has an ABI derived from its specific signature, so this function // must map from the ABI of ExportFuncPtr to the export's signature's ABI. staticbool GenerateInterpEntry(MacroAssembler& masm, const FuncExport& fe, const FuncType& funcType, const Maybe<ImmPtr>& funcPtr,
Offsets* offsets) {
AutoCreatedBy acb(masm, "GenerateInterpEntry");
AssertExpectedSP(masm);
// UBSAN expects that the word before a C++ function pointer is readable for // some sort of generated assertion. // // These interp entry points can sometimes be output at the beginning of a // code page allocation, which will cause access violations when called with // UBSAN enabled. // // Insert some padding in this case by inserting a breakpoint before we align // our code. This breakpoint will misalign the code buffer (which was aligned // due to being at the beginning of the buffer), which will then be aligned // and have at least one word of padding before this entry point. if (masm.currentOffset() == 0) {
masm.breakpoint();
}
masm.haltingAlign(CodeAlignment);
// Double check that the first word is available for UBSAN; see above.
static_assert(CodeAlignment >= sizeof(uintptr_t));
MOZ_ASSERT_IF(!masm.oom(), masm.currentOffset() >= sizeof(uintptr_t));
offsets->begin = masm.currentOffset();
// Save the return address if it wasn't already saved by the call insn. #ifdef JS_USE_LINK_REGISTER # ifdefined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64)
masm.pushReturnAddress(); # elif defined(JS_CODEGEN_ARM64) // WasmPush updates framePushed() unlike pushReturnAddress(), but that's // cancelled by the setFramePushed() below.
WasmPush(masm, lr); # else
MOZ_CRASH("Implement this"); # endif #endif
// Save all caller non-volatile registers before we clobber them here and in // the wasm callee (which does not preserve non-volatile registers).
masm.setFramePushed(0);
masm.PushRegsInMask(NonVolatileRegs);
// Put the 'argv' argument into a non-argument/return/instance register so // that we can use 'argv' while we fill in the arguments for the wasm callee. // Use a second non-argument/return register as temporary scratch. Register argv = ABINonArgReturnReg0; Register scratch = ABINonArgReturnReg1;
// scratch := SP
masm.moveStackPtrTo(scratch);
// Dynamically align the stack since ABIStackAlignment is not necessarily // WasmStackAlignment. Preserve SP so it can be restored after the call. #ifdef JS_CODEGEN_ARM64
static_assert(WasmStackAlignment == 16, "ARM64 SP alignment"); #else
masm.andToStackPtr(Imm32(~(WasmStackAlignment - 1))); #endif
masm.assertStackAlignment(WasmStackAlignment);
// Create a fake frame: just previous RA and an FP. const size_t FakeFrameSize = 2 * sizeof(void*); #ifdef JS_CODEGEN_ARM64
masm.Ldr(ARMRegister(ABINonArgReturnReg0, 64),
MemOperand(ARMRegister(scratch, 64), nonVolatileRegsPushSize)); #else
masm.Push(Address(scratch, nonVolatileRegsPushSize)); #endif // Store fake wasm register state. Ensure the frame pointer passed by the C++ // caller doesn't have the ExitFPTag bit set to not confuse frame iterators. // This bit shouldn't be set if C++ code is using frame pointers, so this has // no effect on native stack unwinders.
masm.andPtr(Imm32(int32_t(~ExitFPTag)), FramePointer); #ifdef JS_CODEGEN_ARM64
masm.asVIXL().Push(ARMRegister(ABINonArgReturnReg0, 64),
ARMRegister(FramePointer, 64));
masm.moveStackPtrTo(FramePointer); #else
masm.Push(FramePointer); #endif
// Read the arguments of wasm::ExportFuncPtr according to the native ABI. // The entry stub's frame is 1 word. constunsigned argBase = sizeof(void*) + nonVolatileRegsPushSize;
ABIArgGenerator abi;
ABIArg arg;
// Align (missing) results area to WasmStackAlignment boudary. Return calls // expect arguments to not overlap with results or other slots. unsigned aligned =
AlignBytes(masm.framePushed() + FakeFrameSize, WasmStackAlignment);
masm.reserveStack(aligned - masm.framePushed() + FakeFrameSize);
// Reserve stack space for the wasm call. unsigned argDecrement = StackDecrementForCall(
WasmStackAlignment, aligned, StackArgBytesForWasmABI(funcType));
masm.reserveStack(argDecrement);
// Copy parameters out of argv and into the wasm ABI registers/stack-slots.
SetupABIArguments(masm, fe, funcType, argv, scratch);
// Call into the real function. Note that, due to the throw stub, fp, instance // and pinned registers may be clobbered.
masm.assertStackAlignment(WasmStackAlignment);
CallFuncExport(masm, fe, funcPtr);
masm.assertStackAlignment(WasmStackAlignment);
// Set the return value based on whether InstanceReg is the // InterpFailInstanceReg magic value (set by the exception handler).
Label success, join;
masm.branchPtr(Assembler::NotEqual, InstanceReg, Imm32(InterpFailInstanceReg),
&success);
masm.move32(Imm32(false), scratch);
masm.jump(&join);
masm.bind(&success);
masm.move32(Imm32(true), scratch);
masm.bind(&join);
// Pop the arguments pushed after the dynamic alignment.
masm.setFramePushed(frameSizeBeforeCall);
masm.freeStackTo(frameSizeBeforeCall);
// Recover the 'argv' pointer which was saved before aligning the stack.
WasmPop(masm, argv);
WasmPop(masm, InstanceReg);
// Pop the stack pointer to its value right before dynamic alignment. #ifdef JS_CODEGEN_ARM64
static_assert(WasmStackAlignment == 16, "ARM64 SP alignment");
masm.setFramePushed(FakeFrameSize);
masm.freeStack(FakeFrameSize); #else
masm.PopStackPtr(); #endif
// Store the register result, if any, in argv[0]. // No widening is required, as the value leaves ReturnReg.
StoreRegisterResult(masm, fe, funcType, argv);
masm.move32(scratch, ReturnReg);
// Restore clobbered non-volatile registers of the caller.
masm.setFramePushed(nonVolatileRegsPushSize);
masm.PopRegsInMask(NonVolatileRegs);
MOZ_ASSERT(masm.framePushed() == 0);
// Creates a JS fake exit frame for wasm, so the frame iterators just use // JSJit frame iteration. // // Note: the caller must ensure InstanceReg is valid. staticvoid GenerateJitEntryThrow(MacroAssembler& masm, unsigned frameSize) {
AssertExpectedSP(masm);
// Helper function for allocating a BigInt and initializing it from an I64 in // GenerateJitEntry. The return result is written to scratch. // // Note that this will create a new frame and must not - in its current form - // be called from a context where there is already another stub frame on the // stack, as that confuses unwinding during profiling. This was a problem for // its use from GenerateImportJitExit, see bug 1754258. Therefore, // FuncType::canHaveJitExit prevents the present function from being called for // exits. staticvoid GenerateBigIntInitialization(MacroAssembler& masm, unsigned bytesPushedByPrologue,
Register64 input, Register scratch, const FuncExport& fe, Label* fail) { #if JS_BITS_PER_WORD == 32
MOZ_ASSERT(input.low != scratch);
MOZ_ASSERT(input.high != scratch); #else
MOZ_ASSERT(input.reg != scratch); #endif
// We need to avoid clobbering other argument registers and the input.
AllocatableRegisterSet regs(RegisterSet::Volatile());
LiveRegisterSet save(regs.asLiveSet());
masm.PushRegsInMask(save);
// Generate a stub that enters wasm from a jit code caller via the jit ABI. // // ARM64 note: This does not save the PseudoStackPointer so we must be sure to // recompute it on every return path, be it normal return or exception return. // The JIT code we return to assumes it is correct.
// The jit caller has set up the following stack layout (sp grows to the // left): // <-- retAddr | descriptor | callee | argc | this | arg1..N // // GenerateJitEntryPrologue has additionally pushed the caller's frame // pointer. The stack pointer is now JitStackAlignment-aligned. // // We initialize an ExitFooterFrame (with ExitFrameType::WasmGenericJitEntry) // immediately below the frame pointer to ensure FP is a valid JS JIT exit // frame.
MOZ_ASSERT(masm.framePushed() == 0);
// Avoid overlapping aligned stack arguments area with ExitFooterFrame. constunsigned AlignedExitFooterFrameSize =
AlignBytes(ExitFooterFrame::Size(), WasmStackAlignment); unsigned normalBytesNeeded =
AlignedExitFooterFrameSize + StackArgBytesForWasmABI(funcType);
// We do two loops: // - one loop up-front will make sure that all the Value tags fit the // expected signature argument types. If at least one inline conversion // fails, we just jump to the OOL path which will call into C++. Inline // conversions are ordered in the way we expect them to happen the most. // - the second loop will unbox the arguments into the right registers.
Label oolCall; for (size_t i = 0; i < funcType.args().length(); i++) {
Address jitArgAddr(FramePointer, JitFrameLayout::offsetOfActualArg(i));
masm.loadValue(jitArgAddr, scratchV);
// Other types (symbol, object, strings) go to the C++ call.
masm.jump(&oolCall);
}
Label storeBack;
// For double inputs, unbox, truncate and store back.
masm.bind(&isDouble);
{
masm.unboxDouble(scratchV, scratchF);
masm.branchTruncateDoubleMaybeModUint32(scratchF, scratchG, &oolCall);
masm.jump(&storeBack);
}
// For null or undefined, store 0.
masm.bind(&isUndefinedOrNull);
{
masm.storeValue(Int32Value(0), jitArgAddr);
masm.jump(&next);
}
// For booleans, store the number value back.
masm.bind(&isBoolean);
masm.unboxBoolean(scratchV, scratchG); // fallthrough:
masm.bind(&storeBack);
masm.storeValue(JSVAL_TYPE_INT32, scratchG, jitArgAddr); break;
} case ValType::I64: { // For BigInt inputs, just skip. Otherwise go to C++ for other // types that require creating a new BigInt or erroring.
masm.branchTestBigInt(Assembler::NotEqual, scratchV, &oolCall); break;
} case ValType::F32: case ValType::F64: { // Note we can reuse the same code for f32/f64 here, since for the // case of f32, the conversion of f64 to f32 will happen in the // second loop.
// Other types (symbol, object, strings) go to the C++ call.
masm.jump(&oolCall);
}
// For int32 and boolean inputs, convert and rebox.
masm.bind(&isInt32OrBoolean);
{
masm.convertInt32ToDouble(scratchV.payloadOrValueReg(), scratchF);
masm.boxDouble(scratchF, jitArgAddr);
masm.jump(&next);
}
// For undefined (missing argument), store NaN.
masm.bind(&isUndefined);
{
masm.storeValue(DoubleValue(JS::GenericNaN()), jitArgAddr);
masm.jump(&next);
}
// +null is 0.
masm.bind(&isNull);
{
masm.storeValue(DoubleValue(0.), jitArgAddr);
} break;
} case ValType::Ref: { // Guarded against by temporarilyUnsupportedReftypeForEntry()
MOZ_RELEASE_ASSERT(funcType.args()[i].refType().isExtern());
masm.branchValueConvertsToWasmAnyRefInline(scratchV, scratchG, scratchF,
&next);
masm.jump(&oolCall); break;
} case ValType::V128: { // Guarded against by hasUnexposableArgOrRet()
MOZ_CRASH("unexpected argument type when calling from the jit");
} default: {
MOZ_CRASH("unexpected argument type when calling from the jit");
}
}
masm.nopAlign(CodeAlignment);
masm.bind(&next);
}
// Call into the real function.
masm.assertStackAlignment(WasmStackAlignment);
CallFuncExport(masm, fe, funcPtr);
masm.assertStackAlignment(WasmStackAlignment);
// Generate an OOL call to the C++ conversion path. bool hasFallThroughForException = false; if (oolCall.used()) {
masm.bind(&oolCall);
masm.setFramePushed(frameSize);
// Baseline and Ion call C++ runtime via BuiltinThunk with wasm abi, so to // unify the BuiltinThunk's interface we call it here with wasm abi.
jit::WasmABIArgIter<MIRTypeVector> argsIter(coerceArgTypes);
// argument 0: function index. if (argsIter->kind() == ABIArg::GPR) {
masm.movePtr(ImmWord(fe.funcIndex()), argsIter->gpr());
} else {
masm.storePtr(ImmWord(fe.funcIndex()),
Address(sp, argsIter->offsetFromArgBase()));
}
argsIter++;
// Note, if code here pushes a reference value into the frame for its own // purposes (and not just as an argument to the callee) then the frame must be // traced in TraceJitExitFrame, see the case there for DirectWasmJitCall. The // callee will trace values that are pushed as arguments, however.
// Push a special frame descriptor that indicates the frame size so we can // directly iterate from the current JIT frame without an extra call. // Note: buildFakeExitFrame pushes an ExitFrameLayout containing the current // frame pointer. We also use this to restore the frame pointer after the // call.
*callOffset = masm.buildFakeExitFrame(scratch); // FP := ExitFrameLayout*
masm.moveStackPtrTo(FramePointer);
size_t framePushedAtFakeFrame = masm.framePushed();
masm.setFramePushed(0);
masm.loadJSContext(scratch);
masm.enterFakeExitFrame(scratch, scratch, ExitFrameType::DirectWasmJitCall);
const JitCallStackArg& stackArg = stackArgs[iter.index()]; switch (stackArg.tag()) { case JitCallStackArg::Tag::Imm32:
GenPrintf(DebugChannel::Function, masm, "%d ", stackArg.imm32());
masm.storePtr(ImmWord(stackArg.imm32()), dst); break; case JitCallStackArg::Tag::GPR:
MOZ_ASSERT(stackArg.gpr() != scratch);
MOZ_ASSERT(stackArg.gpr() != FramePointer);
GenPrintIsize(DebugChannel::Function, masm, stackArg.gpr());
masm.storePtr(stackArg.gpr(), dst); break; case JitCallStackArg::Tag::FPU: switch (iter.mirType()) { case MIRType::Double:
GenPrintF64(DebugChannel::Function, masm, stackArg.fpu());
masm.storeDouble(stackArg.fpu(), dst); break; case MIRType::Float32:
GenPrintF32(DebugChannel::Function, masm, stackArg.fpu());
masm.storeFloat32(stackArg.fpu(), dst); break; default:
MOZ_CRASH( "unexpected MIR type for a float register in wasm fast call");
} break; case JitCallStackArg::Tag::Address: { // The address offsets were valid *before* we pushed our frame.
Address src = stackArg.addr();
MOZ_ASSERT(src.base == masm.getStackPointer());
src.offset += int32_t(framePushedAtFakeFrame + fakeFramePushed -
framePushedAtStart); switch (iter.mirType()) { case MIRType::Double: {
ScratchDoubleScope fpscratch(masm);
GenPrintF64(DebugChannel::Function, masm, fpscratch);
masm.loadDouble(src, fpscratch);
masm.storeDouble(fpscratch, dst); break;
} case MIRType::Float32: {
ScratchFloat32Scope fpscratch(masm);
masm.loadFloat32(src, fpscratch);
GenPrintF32(DebugChannel::Function, masm, fpscratch);
masm.storeFloat32(fpscratch, dst); break;
} case MIRType::Int32: {
masm.loadPtr(src, scratch);
GenPrintIsize(DebugChannel::Function, masm, scratch);
masm.storePtr(scratch, dst); break;
} case MIRType::WasmAnyRef: {
masm.loadPtr(src, scratch);
GenPrintPtr(DebugChannel::Function, masm, scratch);
masm.storePtr(scratch, dst); break;
} case MIRType::StackResults: {
MOZ_CRASH("multi-value in ion to wasm fast path unimplemented");
} default: {
MOZ_CRASH("unexpected MIR type for a stack slot in wasm fast call");
}
} break;
} case JitCallStackArg::Tag::Undefined: {
MOZ_CRASH("can't happen because of arg.kind() check");
}
}
}
GenPrintf(DebugChannel::Function, masm, "\n");
// Load instance; from now on, InstanceReg is live.
masm.movePtr(ImmPtr(&inst), InstanceReg);
masm.storePtr(InstanceReg, Address(masm.getStackPointer(),
WasmCalleeInstanceOffsetBeforeCall));
masm.loadWasmPinnedRegsFromInstance(mozilla::Nothing());
masm.assertStackAlignment(WasmStackAlignment);
MoveSPForJitABI(masm);
masm.callJit(ImmPtr(callee)); #ifdef JS_CODEGEN_ARM64 // WASM does not always keep PSP in sync with SP. So reinitialize it as it // might be clobbered either by WASM or by any C++ calls within.
masm.initPseudoStackPtr(); #endif
masm.freeStackTo(fakeFramePushed);
masm.assertStackAlignment(WasmStackAlignment);
// Store the return value in the appropriate place.
GenPrintf(DebugChannel::Function, masm, "wasm-function[%d]; returns ",
fe.funcIndex()); const ValTypeVector& results = funcType.results(); if (results.length() == 0) {
masm.moveValue(UndefinedValue(), JSReturnOperand);
GenPrintf(DebugChannel::Function, masm, "void");
} else {
MOZ_ASSERT(results.length() == 1, "multi-value return to JS unimplemented"); switch (results[0].kind()) { case wasm::ValType::I32: // The return value is in ReturnReg, which is what Ion expects.
GenPrintIsize(DebugChannel::Function, masm, ReturnReg); #ifdef JS_64BIT
masm.widenInt32(ReturnReg); #endif break; case wasm::ValType::I64: // The return value is in ReturnReg64, which is what Ion expects.
GenPrintI64(DebugChannel::Function, masm, ReturnReg64); break; case wasm::ValType::F32:
masm.canonicalizeFloat(ReturnFloat32Reg);
GenPrintF32(DebugChannel::Function, masm, ReturnFloat32Reg); break; case wasm::ValType::F64:
masm.canonicalizeDouble(ReturnDoubleReg);
GenPrintF64(DebugChannel::Function, masm, ReturnDoubleReg); break; case wasm::ValType::Ref:
GenPrintPtr(DebugChannel::Import, masm, ReturnReg); // The call to wasm above preserves the InstanceReg, we don't // need to reload it here.
masm.convertWasmAnyRefToValue(InstanceReg, ReturnReg, JSReturnOperand,
WasmJitEntryReturnScratch); break; case wasm::ValType::V128:
MOZ_CRASH("unexpected return type when calling from ion to wasm");
}
}
staticvoid FillArgumentArrayForInterpExit(MacroAssembler& masm, unsigned funcImportIndex, const FuncType& funcType, unsigned argOffset, Register scratch) { // This is `sizeof(FrameWithInstances) - ShadowStackSpace` because the latter // is accounted for by the ABIArgIter. constunsigned offsetFromFPToCallerStackArgs = sizeof(FrameWithInstances) - jit::ShadowStackSpace;
MIRType type = i.mirType();
MOZ_ASSERT(args.isSyntheticStackResultPointerArg(i.index()) ==
(type == MIRType::StackResults)); switch (i->kind()) { case ABIArg::GPR: if (type == MIRType::Int32) {
GenPrintIsize(DebugChannel::Import, masm, i->gpr());
masm.store32(i->gpr(), dst);
} elseif (type == MIRType::Int64) {
GenPrintI64(DebugChannel::Import, masm, i->gpr64());
masm.store64(i->gpr64(), dst);
} elseif (type == MIRType::WasmAnyRef) {
GenPrintPtr(DebugChannel::Import, masm, i->gpr());
masm.storePtr(i->gpr(), dst);
} elseif (type == MIRType::StackResults) {
GenPrintPtr(DebugChannel::Import, masm, i->gpr());
masm.storePtr(i->gpr(), dst);
} else {
MOZ_CRASH( "FillArgumentArrayForInterpExit, ABIArg::GPR: unexpected type");
} break; #ifdef JS_CODEGEN_REGISTER_PAIR case ABIArg::GPR_PAIR: if (type == MIRType::Int64) {
GenPrintI64(DebugChannel::Import, masm, i->gpr64());
masm.store64(i->gpr64(), dst);
} else {
MOZ_CRASH("wasm uses hardfp for function calls.");
} break; #endif case ABIArg::FPU: {
FloatRegister srcReg = i->fpu(); if (type == MIRType::Double) {
GenPrintF64(DebugChannel::Import, masm, srcReg);
masm.storeDouble(srcReg, dst);
} elseif (type == MIRType::Float32) { // Preserve the NaN pattern in the input.
GenPrintF32(DebugChannel::Import, masm, srcReg);
masm.storeFloat32(srcReg, dst);
} elseif (type == MIRType::Simd128) { // The value should never escape; the call will be stopped later as // the import is being called. But we should generate something sane // here for the boxed case since a debugger or the stack walker may // observe something.
ScratchDoubleScope dscratch(masm);
masm.loadConstantDouble(0, dscratch);
GenPrintF64(DebugChannel::Import, masm, dscratch);
masm.storeDouble(dscratch, dst);
} else {
MOZ_CRASH("Unknown MIRType in wasm exit stub");
} break;
} case ABIArg::Stack: {
Address src(FramePointer,
offsetFromFPToCallerStackArgs + i->offsetFromArgBase()); if (type == MIRType::Simd128) { // As above. StackCopy does not know this trick.
ScratchDoubleScope dscratch(masm);
masm.loadConstantDouble(0, dscratch);
GenPrintF64(DebugChannel::Import, masm, dscratch);
masm.storeDouble(dscratch, dst);
} else {
StackCopy(masm, type, scratch, src, dst);
} break;
} case ABIArg::Uninitialized:
MOZ_CRASH("Uninitialized ABIArg kind");
}
}
GenPrintf(DebugChannel::Import, masm, "\n");
}
// Note, this may destroy the values in incoming argument registers as a result // of Spectre mitigation. staticvoid FillArgumentArrayForJitExit(MacroAssembler& masm, Register instance, unsigned funcImportIndex, const FuncType& funcType, unsigned argOffset, Register scratch, Register scratch2, Label* throwLabel) {
MOZ_ASSERT(scratch != scratch2);
// This is `sizeof(FrameWithInstances) - ShadowStackSpace` because the latter // is accounted for by the ABIArgIter. constunsigned offsetFromFPToCallerStackArgs = sizeof(FrameWithInstances) - jit::ShadowStackSpace;
// This loop does not root the values that are being constructed in // for the arguments. Allocations that are generated by code either // in the loop or called from it should be NoGC allocations.
GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; arguments ",
funcImportIndex);
MIRType type = i.mirType();
MOZ_ASSERT(args.isSyntheticStackResultPointerArg(i.index()) ==
(type == MIRType::StackResults)); switch (i->kind()) { case ABIArg::GPR: if (type == MIRType::Int32) {
GenPrintIsize(DebugChannel::Import, masm, i->gpr());
masm.storeValue(JSVAL_TYPE_INT32, i->gpr(), dst);
} elseif (type == MIRType::Int64) { // FuncType::canHaveJitExit should prevent this. Also see comments // at GenerateBigIntInitialization.
MOZ_CRASH("Should not happen");
} elseif (type == MIRType::WasmAnyRef) { // This works also for FuncRef because it is distinguishable from // a boxed AnyRef.
masm.movePtr(i->gpr(), scratch2);
GenPrintPtr(DebugChannel::Import, masm, scratch2);
masm.convertWasmAnyRefToValue(instance, scratch2, dst, scratch);
} elseif (type == MIRType::StackResults) {
MOZ_CRASH("Multi-result exit to JIT unimplemented");
} else {
MOZ_CRASH( "FillArgumentArrayForJitExit, ABIArg::GPR: unexpected type");
} break; #ifdef JS_CODEGEN_REGISTER_PAIR case ABIArg::GPR_PAIR: if (type == MIRType::Int64) { // FuncType::canHaveJitExit should prevent this. Also see comments // at GenerateBigIntInitialization.
MOZ_CRASH("Should not happen");
} else {
MOZ_CRASH("wasm uses hardfp for function calls.");
} break; #endif case ABIArg::FPU: {
FloatRegister srcReg = i->fpu(); if (type == MIRType::Double) { // Preserve the NaN pattern in the input.
ScratchDoubleScope fpscratch(masm);
masm.moveDouble(srcReg, fpscratch);
masm.canonicalizeDouble(fpscratch);
GenPrintF64(DebugChannel::Import, masm, fpscratch);
masm.boxDouble(fpscratch, dst);
} elseif (type == MIRType::Float32) { // JS::Values can't store Float32, so convert to a Double.
ScratchDoubleScope fpscratch(masm);
masm.convertFloat32ToDouble(srcReg, fpscratch);
masm.canonicalizeDouble(fpscratch);
GenPrintF64(DebugChannel::Import, masm, fpscratch);
masm.boxDouble(fpscratch, dst);
} elseif (type == MIRType::Simd128) { // The value should never escape; the call will be stopped later as // the import is being called. But we should generate something sane // here for the boxed case since a debugger or the stack walker may // observe something.
ScratchDoubleScope dscratch(masm);
masm.loadConstantDouble(0, dscratch);
GenPrintF64(DebugChannel::Import, masm, dscratch);
masm.boxDouble(dscratch, dst);
} else {
MOZ_CRASH("Unknown MIRType in wasm exit stub");
} break;
} case ABIArg::Stack: {
Address src(FramePointer,
offsetFromFPToCallerStackArgs + i->offsetFromArgBase()); if (type == MIRType::Int32) {
masm.load32(src, scratch);
GenPrintIsize(DebugChannel::Import, masm, scratch);
masm.storeValue(JSVAL_TYPE_INT32, scratch, dst);
} elseif (type == MIRType::Int64) { // FuncType::canHaveJitExit should prevent this. Also see comments // at GenerateBigIntInitialization.
MOZ_CRASH("Should not happen");
} elseif (type == MIRType::WasmAnyRef) { // This works also for FuncRef because it is distinguishable from a // boxed AnyRef.
masm.loadPtr(src, scratch);
GenPrintPtr(DebugChannel::Import, masm, scratch);
masm.convertWasmAnyRefToValue(instance, scratch, dst, scratch2);
} elseif (IsFloatingPointType(type)) {
ScratchDoubleScope dscratch(masm);
FloatRegister fscratch = dscratch.asSingle(); if (type == MIRType::Float32) {
masm.loadFloat32(src, fscratch);
masm.convertFloat32ToDouble(fscratch, dscratch);
} else {
masm.loadDouble(src, dscratch);
}
masm.canonicalizeDouble(dscratch);
GenPrintF64(DebugChannel::Import, masm, dscratch);
masm.boxDouble(dscratch, dst);
} elseif (type == MIRType::Simd128) { // The value should never escape; the call will be stopped later as // the import is being called. But we should generate something // sane here for the boxed case since a debugger or the stack walker // may observe something.
ScratchDoubleScope dscratch(masm);
masm.loadConstantDouble(0, dscratch);
GenPrintF64(DebugChannel::Import, masm, dscratch);
masm.boxDouble(dscratch, dst);
} else {
MOZ_CRASH( "FillArgumentArrayForJitExit, ABIArg::Stack: unexpected type");
} break;
} case ABIArg::Uninitialized:
MOZ_CRASH("Uninitialized ABIArg kind");
}
}
GenPrintf(DebugChannel::Import, masm, "\n");
}
// In debug builds, we'll always have a stack map, even if there are no // refs to track.
MOZ_ASSERT(stackMap); if (stackMap &&
!stackMaps->add((uint8_t*)(uintptr_t)trapInsnOffset.offset(), stackMap)) {
stackMap->destroy(); returnfalse;
} returntrue;
}
// Generate a wrapper function with the standard intra-wasm call ABI which // simply calls an import. This wrapper function allows any import to be treated // like a normal wasm function for the purposes of exports and table calls. In // particular, the wrapper function provides: // - a table entry, so JS imports can be put into tables // - normal entries, so that, if the import is re-exported, an entry stub can // be generated and called without any special cases staticbool GenerateImportFunction(jit::MacroAssembler& masm,
uint32_t funcImportInstanceOffset, const FuncType& funcType,
CallIndirectId callIndirectId,
FuncOffsets* offsets, StackMaps* stackMaps) {
AutoCreatedBy acb(masm, "wasm::GenerateImportFunction");
// The argument register state is already setup by our caller. We just need // to be sure not to clobber it before the call. Register scratch = ABINonArgReg0;
// Copy our frame's stack arguments to the callee frame's stack argument. // // Note offsetFromFPToCallerStackArgs is sizeof(Frame) because the // WasmABIArgIter accounts for both the ShadowStackSpace and the instance // fields of FrameWithInstances. unsigned offsetFromFPToCallerStackArgs = sizeof(Frame);
ArgTypeVector args(funcType); for (WasmABIArgIter i(args); !i.done(); i++) { if (i->kind() != ABIArg::Stack) { continue;
}
// Restore the instance register and pinned regs, per wasm function ABI.
masm.loadPtr(
Address(masm.getStackPointer(), framePushed - sizeOfInstanceSlot),
InstanceReg);
masm.loadWasmPinnedRegsFromInstance(mozilla::Nothing());
// Generate a stub that is called via the internal ABI derived from the // signature of the import and calls into an appropriate callImport C++ // function, having boxed all the ABI arguments into a homogeneous Value array. staticbool GenerateImportInterpExit(MacroAssembler& masm, const FuncImport& fi, const FuncType& funcType,
uint32_t funcImportIndex,
Label* throwLabel,
CallableOffsets* offsets) {
AutoCreatedBy acb(masm, "GenerateImportInterpExit");
// At the point of the call, the stack layout is: // // | stack args | padding | argv[] | padding | retaddr | caller stack | ... // ^ // +-- sp // // The padding between stack args and argv ensures that argv is aligned on a // Value boundary. The padding between argv and retaddr ensures that sp is // aligned. The caller stack includes a ShadowStackArea and the instance // fields before the args, see WasmFrame.h. // // The 'double' alignment is correct since the argv[] is a Value array. unsigned argOffset =
AlignBytes(StackArgBytesForNativeABI(invokeArgTypes), sizeof(double)); // The abiArgCount includes a stack result pointer argument if needed. unsigned abiArgCount = ArgTypeVector(funcType).lengthWithStackResults(); unsigned argBytes = std::max<size_t>(1, abiArgCount) * sizeof(Value); unsigned framePushed =
StackDecrementForCall(ABIStackAlignment, sizeof(Frame), // pushed by prologue
argOffset + argBytes);
// Make the call, test whether it succeeded, and extract the return value.
AssertStackAlignment(masm, ABIStackAlignment);
masm.call(SymbolicAddress::CallImport_General);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
ResultType resultType = ResultType::Vector(funcType.results());
ValType registerResultType; for (ABIResultIter iter(resultType); !iter.done(); iter.next()) { if (iter.cur().inRegister()) {
MOZ_ASSERT(!registerResultType.isValid());
registerResultType = iter.cur().type();
}
} if (!registerResultType.isValid()) {
GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; returns ",
funcImportIndex);
GenPrintf(DebugChannel::Import, masm, "void");
} else { switch (registerResultType.kind()) { case ValType::I32:
masm.load32(argv, ReturnReg); // No widening is required, as we know the value comes from an i32 load.
GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; returns ",
funcImportIndex);
GenPrintIsize(DebugChannel::Import, masm, ReturnReg); break; case ValType::I64:
masm.load64(argv, ReturnReg64);
GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; returns ",
funcImportIndex);
GenPrintI64(DebugChannel::Import, masm, ReturnReg64); break; case ValType::V128: // Note, CallImport_Rtt/V128 currently always throws, so we should never // reach this point.
masm.breakpoint(); break; case ValType::F32:
masm.loadFloat32(argv, ReturnFloat32Reg);
GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; returns ",
funcImportIndex);
GenPrintF32(DebugChannel::Import, masm, ReturnFloat32Reg); break; case ValType::F64:
masm.loadDouble(argv, ReturnDoubleReg);
GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; returns ",
funcImportIndex);
GenPrintF64(DebugChannel::Import, masm, ReturnDoubleReg); break; case ValType::Ref:
masm.loadPtr(argv, ReturnReg);
GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; returns ",
funcImportIndex);
GenPrintPtr(DebugChannel::Import, masm, ReturnReg); break;
}
}
GenPrintf(DebugChannel::Import, masm, "\n");
// The native ABI preserves the instance, heap and global registers since they // are non-volatile.
MOZ_ASSERT(NonVolatileRegs.has(InstanceReg)); #ifdefined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || \ defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64)
MOZ_ASSERT(NonVolatileRegs.has(HeapReg)); #endif
// Generate a stub that is called via the internal ABI derived from the // signature of the import and calls into a compatible JIT function, // having boxed all the ABI arguments into the JIT stack frame layout. staticbool GenerateImportJitExit(MacroAssembler& masm,
uint32_t funcImportInstanceOffset, const FuncType& funcType, unsigned funcImportIndex,
uint32_t fallbackOffset, Label* throwLabel,
ImportOffsets* offsets) {
AutoCreatedBy acb(masm, "GenerateImportJitExit");
AssertExpectedSP(masm);
masm.setFramePushed(0);
// JIT calls use the following stack layout: // // | WasmToJSJitFrameLayout | this | arg1..N | saved instance | ... // ^ // +-- sp // // The JIT ABI requires that sp be JitStackAlignment-aligned after pushing // the return address and frame pointer.
static_assert(WasmStackAlignment >= JitStackAlignment, "subsumes"); constunsigned sizeOfInstanceSlot = sizeof(void*); constunsigned sizeOfRetAddrAndFP = 2 * sizeof(void*); constunsigned sizeOfPreFrame =
WasmToJSJitFrameLayout::Size() - sizeOfRetAddrAndFP; constunsigned sizeOfThisAndArgs =
(1 + funcType.args().length()) * sizeof(Value); constunsigned totalJitFrameBytes = sizeOfRetAddrAndFP + sizeOfPreFrame +
sizeOfThisAndArgs + sizeOfInstanceSlot; constunsigned jitFramePushed =
StackDecrementForCall(JitStackAlignment, sizeof(Frame), // pushed by prologue
totalJitFrameBytes) -
sizeOfRetAddrAndFP;
// 2. Callee, part 1 -- need the callee register for argument filling, so // record offset here and set up callee later.
size_t calleeArgOffset = argOffset;
argOffset += sizeof(size_t);
MOZ_ASSERT(argOffset == sizeOfPreFrame);
// Preserve instance because the JIT callee clobbers it. const size_t savedInstanceOffset = argOffset;
masm.storePtr(InstanceReg,
Address(masm.getStackPointer(), savedInstanceOffset));
// 2. Callee, part 2 -- now that the register is free, set up the callee. Register callee = ABINonArgReturnReg0; // Live until call
// 2.1. Get the callee. This must be a JSFunction if we're using this JIT // exit.
masm.loadPtr(
Address(InstanceReg, Instance::offsetInData(
funcImportInstanceOffset +
offsetof(FuncImportInstanceData, callable))),
callee);
// 2.2. Save callee.
masm.storePtr(callee, Address(masm.getStackPointer(), calleeArgOffset));
// 5. Check if we need to rectify arguments.
masm.loadFunctionArgCount(callee, scratch);
// Note that there might be a GC thing in the JSReturnOperand now. // In all the code paths from here: // - either the value is unboxed because it was a primitive and we don't // need to worry about rooting anymore. // - or the value needs to be rooted, but nothing can cause a GC between // here and CoerceInPlace, which roots before coercing to a primitive.
// The JIT callee clobbers all registers other than the frame pointer, so // restore InstanceReg here.
AssertStackAlignment(masm, JitStackAlignment, sizeOfRetAddrAndFP);
masm.loadPtr(Address(masm.getStackPointer(), savedInstanceOffset),
InstanceReg);
// The frame was aligned for the JIT ABI such that // (sp - 2 * sizeof(void*)) % JitStackAlignment == 0 // But now we possibly want to call one of several different C++ functions, // so subtract 2 * sizeof(void*) so that sp is aligned for an ABI call.
static_assert(ABIStackAlignment <= JitStackAlignment, "subsumes");
masm.reserveStack(sizeOfRetAddrAndFP); unsigned nativeFramePushed = masm.framePushed();
AssertStackAlignment(masm, ABIStackAlignment);
Label oolConvert; const ValTypeVector& results = funcType.results(); if (results.length() == 0) {
GenPrintf(DebugChannel::Import, masm, "void");
} else {
MOZ_ASSERT(results.length() == 1, "multi-value return unimplemented"); switch (results[0].kind()) { case ValType::I32: // No widening is required, as the return value does not come to us in // ReturnReg.
masm.truncateValueToInt32(JSReturnOperand, ReturnDoubleReg, ReturnReg,
&oolConvert);
GenPrintIsize(DebugChannel::Import, masm, ReturnReg); break; case ValType::I64: // No fastpath for now, go immediately to ool case
masm.jump(&oolConvert); break; case ValType::V128: // Unreachable as callImport should not call the stub.
masm.breakpoint(); break; case ValType::F32:
masm.convertValueToFloat32(JSReturnOperand, ReturnFloat32Reg,
&oolConvert);
GenPrintF32(DebugChannel::Import, masm, ReturnFloat32Reg); break; case ValType::F64:
masm.convertValueToDouble(JSReturnOperand, ReturnDoubleReg,
&oolConvert);
GenPrintF64(DebugChannel::Import, masm, ReturnDoubleReg); break; case ValType::Ref: // Guarded by temporarilyUnsupportedReftypeForExit()
MOZ_RELEASE_ASSERT(results[0].refType().isExtern());
masm.convertValueToWasmAnyRef(JSReturnOperand, ReturnReg,
ABINonArgDoubleReg, &oolConvert);
GenPrintPtr(DebugChannel::Import, masm, ReturnReg); break;
}
}
if (oolConvert.used()) {
masm.bind(&oolConvert);
masm.setFramePushed(nativeFramePushed);
// Coercion calls use the following stack layout (sp grows to the left): // | args | padding | Value argv[1] | padding | exit Frame |
MIRTypeVector coerceArgTypes;
MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer)); unsigned offsetToCoerceArgv =
AlignBytes(StackArgBytesForNativeABI(coerceArgTypes), sizeof(Value));
MOZ_ASSERT(nativeFramePushed >= offsetToCoerceArgv + sizeof(Value));
AssertStackAlignment(masm, ABIStackAlignment);
// Store return value into argv[0].
masm.storeValue(JSReturnOperand,
Address(masm.getStackPointer(), offsetToCoerceArgv));
// From this point, it's safe to reuse the scratch register (which // might be part of the JSReturnOperand).
// The JIT might have clobbered exitFP at this point. Since there's // going to be a CoerceInPlace call, pretend we're still doing the JIT // call by restoring our tagged exitFP.
SetExitFP(masm, ExitReason::Fixed::ImportJit, scratch);
// Call coercion function. Note that right after the call, the value of // FP is correct because FP is non-volatile in the native ABI.
AssertStackAlignment(masm, ABIStackAlignment); const ValTypeVector& results = funcType.results(); if (results.length() > 0) { // NOTE that once there can be more than one result and we can box some of // the results (as we must for AnyRef), pointer and already-boxed results // must be rooted while subsequent results are boxed.
MOZ_ASSERT(results.length() == 1, "multi-value return unimplemented"); switch (results[0].kind()) { case ValType::I32:
masm.call(SymbolicAddress::CoerceInPlace_ToInt32);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.unboxInt32(Address(masm.getStackPointer(), offsetToCoerceArgv),
ReturnReg); // No widening is required, as we generate a known-good value in a // safe way here. break; case ValType::I64: {
masm.call(SymbolicAddress::CoerceInPlace_ToBigInt);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
Address argv(masm.getStackPointer(), offsetToCoerceArgv);
masm.unboxBigInt(argv, scratch);
masm.loadBigInt64(scratch, ReturnReg64); break;
} case ValType::F64: case ValType::F32:
masm.call(SymbolicAddress::CoerceInPlace_ToNumber);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.unboxDouble(Address(masm.getStackPointer(), offsetToCoerceArgv),
ReturnDoubleReg); if (results[0].kind() == ValType::F32) {
masm.convertDoubleToFloat32(ReturnDoubleReg, ReturnFloat32Reg);
} break; case ValType::Ref: // Guarded by temporarilyUnsupportedReftypeForExit()
MOZ_RELEASE_ASSERT(results[0].refType().isExtern());
masm.call(SymbolicAddress::BoxValue_Anyref);
masm.branchWasmAnyRefIsNull(true, ReturnReg, throwLabel); break; default:
MOZ_CRASH("Unsupported convert type");
}
}
// Maintain the invariant that exitFP is either unset or not set to a // wasm tagged exitFP, per the jit exit contract.
ClearExitFP(masm, scratch);
#ifdefined(JS_CODEGEN_ARM) staticconst LiveRegisterSet RegsToPreserve(
GeneralRegisterSet(Registers::AllMask &
~((Registers::SetType(1) << Registers::sp) |
(Registers::SetType(1) << Registers::pc))),
FloatRegisterSet(FloatRegisters::AllDoubleMask)); # ifdef ENABLE_WASM_SIMD # error "high lanes of SIMD registers need to be saved too." # endif #elifdefined(JS_CODEGEN_MIPS64) staticconst LiveRegisterSet RegsToPreserve(
GeneralRegisterSet(Registers::AllMask &
~((Registers::SetType(1) << Registers::k0) |
(Registers::SetType(1) << Registers::k1) |
(Registers::SetType(1) << Registers::sp) |
(Registers::SetType(1) << Registers::zero))),
FloatRegisterSet(FloatRegisters::AllDoubleMask)); # ifdef ENABLE_WASM_SIMD # error "high lanes of SIMD registers need to be saved too." # endif #elifdefined(JS_CODEGEN_LOONG64) staticconst LiveRegisterSet RegsToPreserve(
GeneralRegisterSet(Registers::AllMask &
~((uint32_t(1) << Registers::tp) |
(uint32_t(1) << Registers::fp) |
(uint32_t(1) << Registers::sp) |
(uint32_t(1) << Registers::zero))),
FloatRegisterSet(FloatRegisters::AllDoubleMask)); # ifdef ENABLE_WASM_SIMD # error "high lanes of SIMD registers need to be saved too." # endif #elifdefined(JS_CODEGEN_RISCV64) staticconst LiveRegisterSet RegsToPreserve(
GeneralRegisterSet(Registers::AllMask &
~((uint32_t(1) << Registers::tp) |
(uint32_t(1) << Registers::fp) |
(uint32_t(1) << Registers::sp) |
(uint32_t(1) << Registers::zero))),
FloatRegisterSet(FloatRegisters::AllDoubleMask)); # ifdef ENABLE_WASM_SIMD # error "high lanes of SIMD registers need to be saved too." # endif #elifdefined(JS_CODEGEN_ARM64) // We assume that traps do not happen while lr is live. This both ensures that // the size of RegsToPreserve is a multiple of 2 (preserving WasmStackAlignment) // and gives us a register to clobber in the return path. staticconst LiveRegisterSet RegsToPreserve(
GeneralRegisterSet(Registers::AllMask &
~((Registers::SetType(1) << RealStackPointer.code()) |
(Registers::SetType(1) << Registers::lr))), # ifdef ENABLE_WASM_SIMD
FloatRegisterSet(FloatRegisters::AllSimd128Mask)); # else // If SIMD is not enabled, it's pointless to save/restore the upper 64 // bits of each vector register.
FloatRegisterSet(FloatRegisters::AllDoubleMask)); # endif #elifdefined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) // It's correct to use FloatRegisters::AllMask even when SIMD is not enabled; // PushRegsInMask strips out the high lanes of the XMM registers in this case, // while the singles will be stripped as they are aliased by the larger doubles. staticconst LiveRegisterSet RegsToPreserve(
GeneralRegisterSet(Registers::AllMask &
~(Registers::SetType(1) << Registers::StackPointer)),
FloatRegisterSet(FloatRegisters::AllMask)); #else staticconst LiveRegisterSet RegsToPreserve(
GeneralRegisterSet(0), FloatRegisterSet(FloatRegisters::AllDoubleMask)); # ifdef ENABLE_WASM_SIMD # error "no SIMD support" # endif #endif
// Generate a RegisterOffsets which describes the locations of the GPRs as saved // by GenerateTrapExit. FP registers are ignored. Note that the values // stored in the RegisterOffsets are offsets in words downwards from the top of // the save area. That is, a higher value implies a lower address. void wasm::GenerateTrapExitRegisterOffsets(RegisterOffsets* offsets,
size_t* numWords) { // This is the number of words pushed by the initial WasmPush().
*numWords = WasmPushSize / sizeof(void*);
MOZ_ASSERT(*numWords == TrapExitDummyValueOffsetFromTop + 1);
// And these correspond to the PushRegsInMask() that immediately follows. for (GeneralRegisterBackwardIterator iter(RegsToPreserve.gprs()); iter.more();
++iter) {
offsets->setOffset(*iter, *numWords);
(*numWords)++;
}
}
// Generate a stub which calls WasmReportTrap() and can be executed by having // the signal handler redirect PC from any trapping instruction. staticbool GenerateTrapExit(MacroAssembler& masm, Label* throwLabel,
Offsets* offsets) {
AssertExpectedSP(masm);
masm.haltingAlign(CodeAlignment);
masm.setFramePushed(0);
offsets->begin = masm.currentOffset();
// Traps can only happen at well-defined program points. However, since // traps may resume and the optimal assumption for the surrounding code is // that registers are not clobbered, we need to preserve all registers in // the trap exit. One simplifying assumption is that flags may be clobbered. // Push a dummy word to use as return address below.
WasmPush(masm, ImmWord(TrapExitDummyValue)); unsigned framePushedBeforePreserve = masm.framePushed();
masm.PushRegsInMask(RegsToPreserve); unsigned offsetOfReturnWord = masm.framePushed() - framePushedBeforePreserve;
// We know that StackPointer is word-aligned, but not necessarily // stack-aligned, so we need to align it dynamically. Register preAlignStackPointer = ABINonVolatileReg;
masm.moveStackPtrTo(preAlignStackPointer);
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1))); if (ShadowStackSpace) {
masm.subFromStackPtr(Imm32(ShadowStackSpace));
}
// WasmHandleTrap returns null if control should transfer to the throw stub.
masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
// Otherwise, the return value is the TrapData::resumePC we must jump to. // We must restore register state before jumping, which will clobber // ReturnReg, so store ReturnReg in the above-reserved stack slot which we // use to jump to via ret.
masm.moveToStackPtr(preAlignStackPointer);
masm.storePtr(ReturnReg, Address(masm.getStackPointer(), offsetOfReturnWord));
masm.PopRegsInMask(RegsToPreserve); #ifdef JS_CODEGEN_ARM64
WasmPop(masm, lr);
masm.abiret(); #else
masm.ret(); #endif
return FinishOffsets(masm, offsets);
}
staticvoid ClobberWasmRegsForLongJmp(MacroAssembler& masm, Register jumpReg) { // Get the set of all registers that are allocatable in wasm functions
AllocatableGeneralRegisterSet gprs(GeneralRegisterSet::All());
RegisterAllocator::takeWasmRegisters(gprs); // Remove the instance register from this set as landing pads require it to be // valid
gprs.take(InstanceReg); // Remove a specified register that will be used for the longjmp
gprs.take(jumpReg); // Set all of these registers to zero for (GeneralRegisterIterator iter(gprs.asLiveSet()); iter.more(); ++iter) { Register reg = *iter;
masm.xorPtr(reg, reg);
}
// Get the set of all floating point registers that are allocatable in wasm // functions
AllocatableFloatRegisterSet fprs(FloatRegisterSet::All()); // Set all of these registers to NaN. We attempt for this to be a signalling // NaN, but the bit format for signalling NaNs are implementation defined // and so this is just best effort.
Maybe<FloatRegister> regNaN; for (FloatRegisterIterator iter(fprs.asLiveSet()); iter.more(); ++iter) {
FloatRegister reg = *iter; if (!reg.isDouble()) { continue;
} if (regNaN) {
masm.moveDouble(*regNaN, reg); continue;
}
masm.loadConstantDouble(std::numeric_limits<double>::signaling_NaN(), reg);
regNaN = Some(reg);
}
}
// Generates code to jump to a Wasm catch handler after unwinding the stack. // The |rfe| register stores a pointer to the ResumeFromException struct // allocated on the stack. void wasm::GenerateJumpToCatchHandler(MacroAssembler& masm, Register rfe, Register scratch1, Register scratch2) {
masm.loadPtr(Address(rfe, ResumeFromException::offsetOfInstance()),
InstanceReg);
masm.loadWasmPinnedRegsFromInstance(mozilla::Nothing());
masm.switchToWasmInstanceRealm(scratch1, scratch2);
masm.loadPtr(Address(rfe, ResumeFromException::offsetOfTarget()), scratch1);
masm.loadPtr(Address(rfe, ResumeFromException::offsetOfFramePointer()),
FramePointer);
masm.loadStackPtr(Address(rfe, ResumeFromException::offsetOfStackPointer()));
MoveSPForJitABI(masm);
ClobberWasmRegsForLongJmp(masm, scratch1);
masm.jump(scratch1);
}
// Generate a stub that calls the C++ exception handler. staticbool GenerateThrowStub(MacroAssembler& masm, Label* throwLabel,
Offsets* offsets) { Register scratch1 = ABINonArgReturnReg0;
// Conservatively, the stack pointer can be unaligned and we must align it // dynamically.
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1))); if (ShadowStackSpace) {
masm.subFromStackPtr(Imm32(ShadowStackSpace));
}
// Allocate space for exception or regular resume information.
masm.reserveStack(sizeof(jit::ResumeFromException));
masm.moveStackPtrTo(scratch1);
// WasmHandleThrow unwinds JitActivation::wasmExitFP() and initializes the // ResumeFromException struct we allocated on the stack. // // It returns the address of the JIT's exception handler trampoline that we // should jump to. This trampoline will return to the interpreter entry or // jump to a catch handler.
masm.call(SymbolicAddress::HandleThrow);
// Ensure the ResumeFromException struct is on top of the stack.
masm.freeStack(frameSize);
// Jump to the "return value check" code of the JIT's exception handler // trampoline. On ARM64 ensure PSP matches SP. #ifdef JS_CODEGEN_ARM64
masm.Mov(PseudoStackPointer64, sp); #endif
masm.jump(ReturnReg);
// Generate a stub that handles toggleable enter/leave frame traps or // breakpoints. The stub records the frame pointer (via GenerateExitPrologue) // and saves most of registers, so as to not affect the code generated by // WasmBaselineCompile. staticbool GenerateDebugStub(MacroAssembler& masm, Label* throwLabel,
CallableOffsets* offsets) {
AssertExpectedSP(masm);
masm.haltingAlign(CodeAlignment);
masm.setFramePushed(0);
// Save all registers used between baseline compiler operations.
masm.PushRegsInMask(AllAllocatableRegs);
uint32_t framePushed = masm.framePushed();
// This method might be called with unaligned stack -- aligning and // saving old stack pointer at the top. #ifdef JS_CODEGEN_ARM64 // On ARM64 however the stack is always aligned.
static_assert(ABIStackAlignment == 16, "ARM64 SP alignment"); #else Register scratch = ABINonArgReturnReg0;
masm.moveStackPtrTo(scratch);
masm.subFromStackPtr(Imm32(sizeof(intptr_t)));
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
masm.storePtr(scratch, Address(masm.getStackPointer(), 0)); #endif
if (ShadowStackSpace) {
masm.subFromStackPtr(Imm32(ShadowStackSpace));
}
masm.assertStackAlignment(ABIStackAlignment);
masm.call(SymbolicAddress::HandleDebugTrap);
masm.branchIfFalseBool(ReturnReg, throwLabel);
if (ShadowStackSpace) {
masm.addToStackPtr(Imm32(ShadowStackSpace));
} #ifndef JS_CODEGEN_ARM64
masm.Pop(scratch);
masm.moveToStackPtr(scratch); #endif
staticbool GenerateRequestTierUpStub(MacroAssembler& masm,
CallableOffsets* offsets) { // This is similar to GenerateDebugStub. As with that routine, all registers // are saved, we call out to a C++ helper, then restore the registers. The // helper can't fail, though. // // On entry to (the code generated by) this routine, we expect the requesting // instance pointer to be in InstanceReg, regardless of the platform.
// Save all registers used between baseline compiler operations.
masm.PushRegsInMask(AllAllocatableRegs);
uint32_t framePushed = masm.framePushed();
// This method might be called with unaligned stack -- aligning and // saving old stack pointer at the top. #ifdef JS_CODEGEN_ARM64 // On ARM64 however the stack is always aligned.
static_assert(ABIStackAlignment == 16, "ARM64 SP alignment"); #else Register scratch = ABINonArgReturnReg0;
masm.moveStackPtrTo(scratch);
masm.subFromStackPtr(Imm32(sizeof(intptr_t)));
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
masm.storePtr(scratch, Address(masm.getStackPointer(), 0)); #endif
if (ShadowStackSpace > 0) {
masm.subFromStackPtr(Imm32(ShadowStackSpace));
}
masm.assertStackAlignment(ABIStackAlignment);
// Pass InstanceReg as the first (and only) arg to the C++ routine. We // expect that the only target to pass the first integer arg in memory is // x86_32, and handle that specially.
ABIArgGenerator abi;
ABIArg arg = abi.next(MIRType::Pointer); #ifndef JS_CODEGEN_X86 // The arg rides in a reg.
MOZ_RELEASE_ASSERT(arg.kind() == ABIArg::GPR);
masm.movePtr(InstanceReg, arg.gpr()); #else // Ensure we don't need to consider ShadowStackSpace.
static_assert(ShadowStackSpace == 0); // Ensure the ABIArgGenerator is consistent with the code generation // assumptions we make here.
MOZ_RELEASE_ASSERT(arg.kind() == ABIArg::Stack &&
arg.offsetFromArgBase() == 0); // Get the arg on the stack without messing up the stack alignment.
masm.subFromStackPtr(Imm32(12));
masm.push(InstanceReg); #endif
masm.call(SymbolicAddress::HandleRequestTierUp); // The call can't fail (meaning, if it does fail, we ignore that)
#ifdef JS_CODEGEN_X86 // Remove the arg and padding we just pushed.
masm.addToStackPtr(Imm32(16)); #endif
staticbool GenerateUpdateCallRefMetricsStub(MacroAssembler& masm,
CallableOffsets* offsets) { // This is a stub which is entirely self-contained -- it calls no other // functions, cannot fail, and creates a minimal stack frame. It can only // use three registers, `regMetrics`, `regFuncRef` and `regScratch`, as set // up below, and as described in BaseCompiler::updateCallRefMetrics. All // other registers must remain unchanged. Also, we may read InstanceReg. // // `regMetrics` (the CallRefMetrics*) should satisfy // CallRefMetrics::invariantsOK() both on entry to and exit from the code // generated here.
// `regMetrics` and `regFuncRef` are live at entry, but not `regScratch`. constRegister regMetrics = WasmCallRefCallScratchReg0; // CallRefMetrics* constRegister regFuncRef = WasmCallRefCallScratchReg1; // FuncExtended* constRegister regScratch = WasmCallRefCallScratchReg2; // scratch
// At entry to the stub, `regMetrics` points at the CallRefMetrics, // `regFuncRef` points at the FunctionExtended, `regScratch` is available as // scratch, `regFuncRef` is known to be non-null, and, if the target0/count0 // slot is in use, it is known not to match that slot. The call may or may // not be cross-instance.
// Briefly, what we generate here is: // // assert(regFuncRef is non-null) // // if (regFuncRef is a cross instance call) { // regMetrics->countOther++; // return; // } // // assert(regFuncRef != regMetrics->targets[0]); // // for (i = 1; i < NUM_SLOTS; i++) { // if (regFuncRef == regMetrics->targets[i]) { // regMetrics->counts[i]++; // if (regMetrics->counts[i-1] <u regMetrics->counts[i]) { // // swap regMetrics->counts[i-1]/[i] and // regMetrics->targets[i-1]/[i] // } // return; // } // } // // for (i = 0; i < NUM_SLOTS; i++) { // if (regMetrics->targets[i] is nullptr) { // regMetrics->targets[i] = regFuncRef; // regMetrics->counts[i] = 1; // return; // } // } // // regMetrics->countsOther++; // return; // // And the loops are unrolled.
// Frame setup and unwinding: we generate the absolute minimal frame setup // (`push FP; FP := SP` / `pop FP; ret`). There is no register save/restore // in the frame. The routine created here is a leaf and will neither trap // nor invoke GC, so the unwindability requirements are minimal -- only the // profiler will need to be able to unwind through it.
// See declaration of CallRefMetrics for comments about assignments of // funcrefs to `CallRefMetrics::targets[]` fields.
#ifdef DEBUG // Assertion: we know the target is non-null at entry, because the in-line // code created by BaseCompiler::callRef handles that case. // if (regFuncRef == nullptr) { // crash; // }
Label after1;
masm.branchWasmAnyRefIsNull(/*isNull=*/false, regFuncRef, &after1);
masm.breakpoint();
masm.bind(&after1); #endif
// If it is a cross-instance call, add it to the `countOther` bin. // regScratch = regFuncRef->instance; // if (regScratch != thisInstance) { // regScratch = regMetrics->countOther; // regScratch++; // regMetrics->countOther = regScratch; // return; // }
Label after2; const size_t offsetOfInstanceSlot = FunctionExtended::offsetOfExtendedSlot(
FunctionExtended::WASM_INSTANCE_SLOT);
masm.loadPtr(Address(regFuncRef, offsetOfInstanceSlot), regScratch);
masm.branchPtr(Assembler::Equal, InstanceReg, regScratch, &after2); // const size_t offsetOfCountOther = CallRefMetrics::offsetOfCountOther();
masm.load32(Address(regMetrics, offsetOfCountOther), regScratch);
masm.add32(Imm32(1), regScratch);
masm.store32(regScratch, Address(regMetrics, offsetOfCountOther));
masm.jump(&ret); //
masm.bind(&after2);
#ifdef DEBUG // Assertion: we know it can't be a hit at slot zero, because the inline code // also handles that case. // regScratch = regMetrics->targets[0]; // if (regScratch == regFuncRef) { // crash; // }
Label after3; const size_t offsetOfTarget0 = CallRefMetrics::offsetOfTarget(0);
masm.loadPtr(Address(regMetrics, offsetOfTarget0), regScratch);
masm.branchPtr(Assembler::NotEqual, regScratch, regFuncRef, &after3);
masm.breakpoint();
masm.bind(&after3); #endif
// If it matches slot one, increment count, swap with slot zero if needed // regScratch = regMetrics->targets[1]; // if (regFuncRef == regScratch) { // // We need a second temp register (regScratch being the first). // // We no longer need regFuncRef so use that as the second temp. // regScratch = regMetrics->counts[0]; // regFuncRef = regMetrics->counts[1]; // regFuncRef++; // regMetrics->counts[1] = regFuncRef; // if (regScratch <u regFuncRef) { // // regScratch and regFuncRef // // are regMetrics->counts[0] and [1] respectively // regMetrics->counts[0] = regFuncRef; // regMetrics->counts[1] = regScratch; // regScratch = regMetrics->targets[0]; // regFuncRef = regMetrics->targets[1]; // regMetrics->targets[0] = regFuncRef; // regMetrics->targets[1] = regScratch; // } // return; // } // and the same for slots 2, 3, 4, etc for (size_t i = 1; i < CallRefMetrics::NUM_SLOTS; i++) {
Label after4;
masm.loadPtr(Address(regMetrics, CallRefMetrics::offsetOfTarget(i)),
regScratch);
masm.branchPtr(Assembler::NotEqual, regFuncRef, regScratch, &after4);
// Not found. Use the first unused slot, if available. This assumes that T // is non-null; but that is assured us on entry (and asserted above). See // CallRefMetrics::invariantsOK. // if (regMetrics->targets[0] == nullptr) { // regMetrics->targets[0] = regFuncRef; // regMetrics->counts[0] = 1; // return; // } // and the same for slots 1, 2, 3, 4, etc for (size_t i = 0; i < CallRefMetrics::NUM_SLOTS; i++) {
Label after5;
masm.loadPtr(Address(regMetrics, CallRefMetrics::offsetOfTarget(i)),
regScratch);
masm.branchWasmAnyRefIsNull(/*isNull=*/false, regScratch, &after5);
// Not found, and we don't have a slot with which to track this new target // individually. Instead just increment the "all others" bin. // regScratch = regMetrics->countOther; // regScratch++; // regMetrics->countOther = regScratch; // return;
masm.load32(Address(regMetrics, CallRefMetrics::offsetOfCountOther()),
regScratch);
masm.add32(Imm32(1), regScratch);
masm.store32(regScratch,
Address(regMetrics, CallRefMetrics::offsetOfCountOther()));
#ifdef JS_CODEGEN_ARM64 // Unaligned ABI calls require SP+PSP, but our mode here is SP-only
masm.SetStackPointer64(PseudoStackPointer64);
masm.Mov(PseudoStackPointer64, sp); #endif
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.