// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <algorithm> #include <optional> #include "src/base/utils/random-number-generator.h" #include "src/codegen/assembler-inl.h" #include "src/codegen/code-stub-assembler-inl.h" #include "src/codegen/machine-type.h" #include "src/codegen/macro-assembler-inl.h" #include "src/codegen/optimized-compilation-info.h" #include "src/compiler/backend/code-generator.h" #include "src/compiler/backend/instruction.h" #include "src/compiler/linkage.h" #include "src/execution/isolate.h" #include "src/objects/heap-number-inl.h" #include "src/objects/objects-inl.h" #include "src/objects/smi.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/codegen-tester.h" #include "test/cctest/compiler/function-tester.h" #include "test/common/code-assembler-tester.h" #if V8_ENABLE_WEBASSEMBLY #include "src/compiler/wasm-compiler.h" #include "src/wasm/wasm-engine.h" #endif // V8_ENABLE_WEBASSEMBLY namespace v8 { namespace internal { namespace compiler { #define __ … namespace { enum MoveMode { … }; // Whether the layout before and after the moves must be the same. enum LayoutMode { … }; enum OperandLifetime { … }; int GetSlotSizeInBytes(MachineRepresentation rep) { … } // Forward declaration. Handle<Code> BuildTeardownFunction( Isolate* isolate, CallDescriptor* call_descriptor, const std::vector<AllocatedOperand>& parameters); // Build the `setup` function. It takes a code object and a FixedArray as // parameters and calls the former while passing it each element of the array as // arguments: // ~~~ // FixedArray setup(CodeObject* test, FixedArray state_in) { // FixedArray state_out = AllocateZeroedFixedArray(state_in.length()); // // `test` will tail-call to its first parameter which will be `teardown`. // return test(teardown, state_out, state_in[0], state_in[1], // state_in[2], ...); // } // ~~~ // // This function needs to convert each element of the FixedArray to raw unboxed // values to pass to the `test` function. The array will have been created using // `GenerateInitialState()` and needs to be converted in the following way: // // | Parameter type | FixedArray element | Conversion | // |----------------+---------------------+------------------------------------| // | kTagged | Smi | None. | // | kFloat32 | HeapNumber | Load value and convert to Float32. | // | kFloat64 | HeapNumber | Load value. | // | kSimd128 | FixedArray<Smi>[4] | Untag each Smi and write the | // | | | results into lanes of a new | // | | | 128-bit vector. | // Handle<Code> BuildSetupFunction(Isolate* isolate, CallDescriptor* test_call_descriptor, CallDescriptor* teardown_call_descriptor, std::vector<AllocatedOperand> parameters, const std::vector<AllocatedOperand>& results) { … } // Build the `teardown` function. It takes a FixedArray as argument, fills it // with the rest of its parameters and returns it. The parameters need to be // consistent with `parameters`. // ~~~ // FixedArray teardown(CodeObject* /* unused */, FixedArray result, // // Tagged registers. // Object r0, Object r1, ..., // // FP registers. // Float32 s0, Float64 d1, ..., // // Mixed stack slots. // Float64 mem0, Object mem1, Float32 mem2, ...) { // result[0] = r0; // result[1] = r1; // ... // result[..] = s0; // ... // result[..] = mem0; // ... // return result; // } // ~~~ // // This function needs to convert its parameters into values fit for a // FixedArray, essentially reverting what the `setup` function did: // // | Parameter type | Parameter value | Conversion | // |----------------+-------------------+--------------------------------------| // | kTagged | Smi or HeapNumber | None. | // | kFloat32 | Raw Float32 | Convert to Float64. | // | kFloat64 | Raw Float64 | None. | // | kSimd128 | Raw Simd128 | Split into 4 Word32 values and tag | // | | | them. | // // Note that it is possible for a `kTagged` value to go from a Smi to a // HeapNumber. This is because `AssembleMove` will allocate a new HeapNumber if // it is asked to move a FP constant to a tagged register or slot. // // Finally, it is important that this function does not call `RecordWrite` which // is why "setup" is in charge of all allocations and we are using // UNSAFE_SKIP_WRITE_BARRIER. The reason for this is that `RecordWrite` may // clobber the top 64 bits of Simd128 registers. This is the case on x64, ia32 // and Arm64 for example. Handle<Code> BuildTeardownFunction( Isolate* isolate, CallDescriptor* call_descriptor, const std::vector<AllocatedOperand>& parameters) { … } // Print the content of `value`, representing the register or stack slot // described by `operand`. void PrintStateValue(std::ostream& os, Isolate* isolate, DirectHandle<Object> value, AllocatedOperand operand) { … } bool TestSimd128Moves() { … } } // namespace #undef __ // Representation of a test environment. It describes a set of registers, stack // slots and constants available to the CodeGeneratorTester to perform moves // with. It has the ability to randomly generate lists of moves and run the code // generated by the CodeGeneratorTester. // // The following representations are tested: // - kTagged // - kFloat32 // - kFloat64 // - kSimd128 (if supported) // There is no need to test using Word32 or Word64 as they are the same as // Tagged as far as the code generator is concerned. // // Testing the generated code is achieved by wrapping it around `setup` and // `teardown` functions, written using the CodeStubAssembler. The key idea here // is that `teardown` and the generated code share the same custom // CallDescriptor. This descriptor assigns parameters to either registers or // stack slot of a given representation and therefore essentially describes the // environment. // // What happens is the following: // // - The `setup` function receives a FixedArray as the initial state. It // unpacks it and passes each element as arguments to the generated code // `test`. We also pass the `teardown` function as a first argument as well // as a newly allocated FixedArray as a second argument which will hold the // final results. Thanks to the custom CallDescriptor, registers and stack // slots get initialised according to the content of the initial FixedArray. // // - The `test` function performs the list of moves on its parameters and // eventually tail-calls to its first parameter, which is the `teardown` // function. // // - The `teardown` function receives the final results as a FixedArray, fills // it with the rest of its arguments and returns it. Thanks to the // tail-call, this is as if the `setup` function called `teardown` directly, // except now moves were performed! // // .----------------setup--------------------------. // | Take a FixedArray as parameters with | // | all the initial values of registers | // | and stack slots. | <- CodeStubAssembler // | | // | Allocate a new FixedArray `result` with | // | initial values. | // | | // | Call test(teardown, result, state[0], | // | state[1], state[2], ...); | // '-----------------------------------------------' // | // V // .----------------test-------------------------------. // | - Move(param3, param42); | // | - Swap(param64, param4); | // | - Move(param2, param6); | <- CodeGeneratorTester // | ... | // | | // | // "teardown" is the first parameter as well as | // | // the callee. | // | TailCall teardown(teardown, result, param2, ...); | // '---------------------------------------------------' // | // V // .----------------teardown---------------------------. // | Fill in the incoming `result` FixedArray with all | // | parameters and return it. | <- CodeStubAssembler // '---------------------------------------------------' class TestEnvironment : public HandleAndZoneScope { … }; // static constexpr int TestEnvironment::kGeneralRegisterCount; constexpr int TestEnvironment::kDoubleRegisterCount; constexpr int TestEnvironment::kTaggedSlotCount; constexpr int TestEnvironment::kFloat32SlotCount; constexpr int TestEnvironment::kFloat64SlotCount; constexpr int TestEnvironment::kSimd128SlotCount; constexpr int TestEnvironment::kSmiConstantCount; constexpr int TestEnvironment::kFloatConstantCount; constexpr int TestEnvironment::kDoubleConstantCount; // Wrapper around the CodeGenerator. Code generated by this can // only be called using the given `TestEnvironment`. class CodeGeneratorTester { … }; // The following fuzz tests will assemble a lot of moves, wrap them in // executable native code and run them. In order to check that moves were // performed correctly, we need to setup an environment with an initial state // and get it back after the list of moves were performed. // // We have two components to do this: TestEnvironment and CodeGeneratorTester. // // The TestEnvironment is in charge of bringing up an environment consisting of // a set of registers, stack slots and constants, with initial values in // them. The CodeGeneratorTester is a wrapper around the CodeGenerator and its // only purpose is to generate code for a list of moves. The TestEnvironment is // then able to run this code against the environment and return a resulting // state. // // A "state" here is a packed FixedArray with tagged values which can either be // Smis or HeapNumbers. When calling TestEnvironment::Run(...), registers and // stack slots will be initialised according to this FixedArray. A new // FixedArray is returned containing values that were moved by the generated // code. // // And finally, we are able to compare the resulting FixedArray against a // reference, computed with a simulation of AssembleMove and AssembleSwap. See // SimulateSequentialMoves, SimulateParallelMoves and SimulateSwaps. // Allocate space between slots to increase coverage of moves with larger // ranges. Note that this affects how much stack is allocated when running the // generated code. It means we have to be careful not to exceed the stack limit, // which is lower on Windows. #ifdef V8_OS_WIN constexpr int kExtraSpace = 0; #else constexpr int kExtraSpace = …; #endif TEST(FuzzAssembleMove) { … } // Test integration with the gap resolver by resolving parallel moves first. TEST(FuzzAssembleParallelMove) { … } TEST(FuzzAssembleSwap) { … } TEST(FuzzAssembleMoveAndSwap) { … } TEST(AssembleTailCallGap) { … } #if V8_ENABLE_WEBASSEMBLY namespace { std::shared_ptr<wasm::NativeModule> AllocateNativeModule(Isolate* isolate, size_t code_size) { … } } // namespace // Test stack argument pushing with some gaps that require stack pointer // adjustment. TEST(Regress_1171759) { … } #endif // V8_ENABLE_WEBASSEMBLY } // namespace compiler } // namespace internal } // namespace v8