dart-sdk/runtime/bin/platform.h
Ryan Macnak 04ba20aa98 [vm] Support RISC-V.
Implements a backend targeting RV32GC and RV64GC, based on Linux standardizing around GC. The assembler is written to make it easy to disable usage of C, but because the sizes of some instruction sequences are compile-time constants, an additional build configuration would need to be defined to make use of it.

The assembler and disassembler cover every RV32/64GC instruction. The simulator covers all instructions except accessing CSRs and the floating point state accessible through such, include accrued exceptions and dynamic rounding mode.

Quirks:
  - RISC-V is a compare-and-branch architecture, but some existing "architecture-independent" parts of the Dart compiler assume a condition code architecture. To avoid rewriting these parts, we use a peephole in the assembler to map to compare-and-branch. See Assembler::BranchIf. Luckily nothing depended on taking multiple branches on the same condition code set.
  - There are no hardware overflow checks, so we must use Hacker's Delight style software checks. Often these are very cheap: if the sign of one operand is known, a single branch is needed.
  - The ranges of RISC-V branches and jumps are such that we use 3 levels of generation for forward branches, instead of the 2 levels of near and far branches used on ARM[64]. Nearly all code is handled by the first two levels with 20-bits of range, with enormous regex matchers triggering the third level that uses aupic+jalr to get 32-bits of range.
  - For PC-relative calls in AOT, we always generate auipc+jalr pairs with 32-bits of range, so we never generate trampolines.
  - Only a subset of registers are available in some compressed instructions, so we assign the most popular uses to these registers. In particular, THR, TMP[2], CODE and PP. This has the effect of assigning CODE and PP to volatile registers in the C calling convention, whereas they are assigned preserved registers on the other architectures. As on ARM64, PP is untagged; this is so short indices can be accessed with a compressed instruction.
  - There are no push or pop instructions, so combining pushes and pops is preferred so we can update SP once.
  - The C calling convention has a strongly aligned stack, but unlike on ARM64 we don't need to use an alternate stack pointer. The author ensured language was added to the RISC-V psABI making the OS responsible for realigning the stack pointer for signal handlers, allowing Dart to leave the stack pointer misaligned from the C calling convention's point of view until a foreign call.
  - We don't bother with the link register tracking done on ARM[64]. Instead we make use of an alternate link register to avoid inline spilling in the write barrier.

Unimplemented:
 - non-trivial FFI cases
 - Compressed pointers - No intention to implement.
 - Unboxed SIMD - We might make use of the V extension registers when the V extension is ratified.
 - BigInt intrinsics

TEST=existing tests for IL level, new tests for assembler/disassembler/simulator
Bug: https://github.com/dart-lang/sdk/issues/38587
Bug: https://github.com/dart-lang/sdk/issues/48164
Change-Id: I991d1df4be5bf55efec5371b767b332d37dfa3e0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/217289
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
2022-01-20 00:57:57 +00:00

142 lines
4.5 KiB
C++

// Copyright (c) 2012, 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.
#ifndef RUNTIME_BIN_PLATFORM_H_
#define RUNTIME_BIN_PLATFORM_H_
#include "bin/builtin.h"
#include "platform/atomic.h"
#include "platform/globals.h"
#include "platform/utils.h"
#if defined(DART_HOST_OS_MACOS)
#include "bin/platform_macos.h"
#endif // defined(DART_HOST_OS_MACOS)
namespace dart {
namespace bin {
class Platform {
public:
// Perform platform specific initialization.
static bool Initialize();
// Returns the number of processors on the machine.
static int NumberOfProcessors();
// Returns a string representing the operating system ("linux",
// "macos", "windows", or "android"). The returned string should not be
// deallocated by the caller.
static const char* OperatingSystem();
// Returns a string representing the version of the operating system. The
// format of the string is determined by the platform. The returned string
// should not be deallocated by the caller.
static const char* OperatingSystemVersion();
// Returns the architecture name of the processor the VM is running on
// (ia32, x64, arm, or arm64).
static const char* HostArchitecture() {
#if defined(HOST_ARCH_ARM)
return "arm";
#elif defined(HOST_ARCH_ARM64)
return "arm64";
#elif defined(HOST_ARCH_IA32)
return "ia32";
#elif defined(HOST_ARCH_X64)
return "x64";
#elif defined(HOST_ARCH_RISCV32)
return "riscv32";
#elif defined(HOST_ARCH_RISCV64)
return "riscv64";
#else
#error Architecture detection failed.
#endif
}
static const char* LibraryPrefix();
// Returns a string representing the operating system's shared library
// extension (e.g. 'so', 'dll', ...). The returned string should not be
// deallocated by the caller.
static const char* LibraryExtension();
// Extracts the local hostname.
static bool LocalHostname(char* buffer, intptr_t buffer_length);
static const char* LocaleName();
// Extracts the environment variables for the current process. The array of
// strings is Dart_ScopeAllocated. The number of elements in the array is
// returned in the count argument.
static char** Environment(intptr_t* count);
static const char* ResolveExecutablePath();
// This has the same effect as calling ResolveExecutablePath except that
// Dart_ScopeAllocate is not called and that the result goes into the given
// parameters.
// WARNING: On Fuchsia it returns -1, i.e. doesn't work.
// Note that `result` should be pre-allocated with size `result_size`.
// The return-value is the length read into `result` or -1 on failure.
static intptr_t ResolveExecutablePathInto(char* result, size_t result_size);
// Stores the executable name.
static void SetExecutableName(const char* executable_name) {
executable_name_ = executable_name;
}
static const char* GetExecutableName();
static const char* GetResolvedExecutableName() {
if (resolved_executable_name_.load() == nullptr) {
// Try to resolve the executable path using platform specific APIs.
const char* resolved_name = Platform::ResolveExecutablePath();
if (resolved_name != nullptr) {
char* resolved_name_copy = Utils::StrDup(resolved_name);
const char* expect_old_is_null = nullptr;
if (!resolved_executable_name_.compare_exchange_strong(
expect_old_is_null, resolved_name_copy)) {
free(resolved_name_copy);
}
}
}
return resolved_executable_name_.load();
}
// Stores and gets the flags passed to the executable.
static void SetExecutableArguments(int script_index, char** argv) {
script_index_ = script_index;
argv_ = argv;
}
static int GetScriptIndex() { return script_index_; }
static char** GetArgv() { return argv_; }
static void SetProcessName(const char* name);
DART_NORETURN static void Exit(int exit_code);
static void SetCoreDumpResourceLimit(int value);
private:
// The path to the executable.
static const char* executable_name_;
// The path to the resolved executable.
//
// We use require-release semantics to ensure initializing stores to the
// string are visible when the string becomes visible.
static AcqRelAtomic<const char*> resolved_executable_name_;
static int script_index_;
static char** argv_; // VM flags are argv_[1 ... script_index_ - 1]
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(Platform);
};
} // namespace bin
} // namespace dart
#endif // RUNTIME_BIN_PLATFORM_H_