/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jit/arm/Architecture-arm.h" #if !defined(JS_SIMULATOR_ARM) && !defined(__APPLE__) # include #endif #include #ifdef XP_UNIX # include #endif #if defined(XP_IOS) # include #endif #include "jit/arm/Assembler-arm.h" #include "jit/arm/Simulator-arm.h" #include "jit/FlushICache.h" // js::jit::FlushICache #include "jit/RegisterSets.h" #if !defined(__linux__) || defined(ANDROID) || defined(JS_SIMULATOR_ARM) // The Android NDK and B2G do not include the hwcap.h kernel header, and it is // not defined when building the simulator, so inline the header defines we // need. # define HWCAP_VFP (1 << 6) # define HWCAP_NEON (1 << 12) # define HWCAP_VFPv3 (1 << 13) # define HWCAP_VFPv3D16 (1 << 14) /* also set for VFPv4-D16 */ # define HWCAP_VFPv4 (1 << 16) # define HWCAP_IDIVA (1 << 17) # define HWCAP_IDIVT (1 << 18) # define HWCAP_VFPD32 (1 << 19) /* set if VFP has 32 regs (not 16) */ # define AT_HWCAP 16 #else # include # if !defined(HWCAP_IDIVA) # define HWCAP_IDIVA (1 << 17) # endif # if !defined(HWCAP_VFPD32) # define HWCAP_VFPD32 (1 << 19) /* set if VFP has 32 regs (not 16) */ # endif #endif namespace js { namespace jit { // Parse the Linux kernel cpuinfo features. This is also used to parse the // override features which has some extensions: 'armv7', 'align' and 'hardfp'. static uint32_t ParseARMCpuFeatures(const char* features, bool override = false) { uint32_t flags = 0; // For ease of running tests we want it to be the default to fixup faults. bool fixupAlignmentFault = true; for (;;) { char ch = *features; if (!ch) { // End of string. break; } if (ch == ' ' || ch == ',') { // Skip separator characters. features++; continue; } // Find the end of the token. const char* end = features + 1; for (;; end++) { ch = *end; if (!ch || ch == ' ' || ch == ',') { break; } } size_t count = end - features; if (count == 3 && strncmp(features, "vfp", 3) == 0) { flags |= HWCAP_VFP; } else if (count == 5 && strncmp(features, "vfpv2", 5) == 0) { flags |= HWCAP_VFP; // vfpv2 is the same as vfp } else if (count == 4 && strncmp(features, "neon", 4) == 0) { flags |= HWCAP_NEON; } else if (count == 5 && strncmp(features, "vfpv3", 5) == 0) { flags |= HWCAP_VFPv3; } else if (count == 8 && strncmp(features, "vfpv3d16", 8) == 0) { flags |= HWCAP_VFPv3D16; } else if (count == 5 && strncmp(features, "vfpv4", 5) == 0) { flags |= HWCAP_VFPv4; } else if (count == 5 && strncmp(features, "idiva", 5) == 0) { flags |= HWCAP_IDIVA; } else if (count == 5 && strncmp(features, "idivt", 5) == 0) { flags |= HWCAP_IDIVT; } else if (count == 6 && strncmp(features, "vfpd32", 6) == 0) { flags |= HWCAP_VFPD32; } else if (count == 5 && strncmp(features, "armv7", 5) == 0) { flags |= HWCAP_ARMv7; } else if (count == 5 && strncmp(features, "align", 5) == 0) { flags |= HWCAP_ALIGNMENT_FAULT | HWCAP_FIXUP_FAULT; #if defined(JS_SIMULATOR_ARM) } else if (count == 7 && strncmp(features, "nofixup", 7) == 0) { fixupAlignmentFault = false; } else if (count == 6 && strncmp(features, "hardfp", 6) == 0) { flags |= HWCAP_USE_HARDFP_ABI; #endif } else if (override) { fprintf(stderr, "Warning: unexpected ARM feature at: %s\n", features); } features = end; } if (!fixupAlignmentFault) { flags &= ~HWCAP_FIXUP_FAULT; } return flags; } static uint32_t CanonicalizeARMHwCapFlags(uint32_t flags) { // Canonicalize the flags. These rules are also applied to the features // supplied for simulation. // VFPv3 is a subset of VFPv4, force this if the input string omits it. if (flags & HWCAP_VFPv4) { flags |= HWCAP_VFPv3; } // The VFPv3 feature is expected when the VFPv3D16 is reported, but add it // just in case of a kernel difference in feature reporting. if (flags & HWCAP_VFPv3D16) { flags |= HWCAP_VFPv3; } // VFPv2 is a subset of VFPv3, force this if the input string omits it. VFPv2 // is just an alias for VFP. if (flags & HWCAP_VFPv3) { flags |= HWCAP_VFP; } // If we have Neon we have floating point. if (flags & HWCAP_NEON) { flags |= HWCAP_VFP; } // If VFPv3 or Neon is supported then this must be an ARMv7. if (flags & (HWCAP_VFPv3 | HWCAP_NEON)) { flags |= HWCAP_ARMv7; } // Some old kernels report VFP and not VFPv3, but if ARMv7 then it must be // VFPv3. if ((flags & HWCAP_VFP) && (flags & HWCAP_ARMv7)) { flags |= HWCAP_VFPv3; } // Older kernels do not implement the HWCAP_VFPD32 flag. if ((flags & HWCAP_VFPv3) && !(flags & HWCAP_VFPv3D16)) { flags |= HWCAP_VFPD32; } return flags; } #if !defined(JS_SIMULATOR_ARM) && (defined(__linux__) || defined(ANDROID)) static bool forceDoubleCacheFlush = false; #endif // The override flags parsed from the ARMHWCAP environment variable or from the // --arm-hwcap js shell argument. They are stable after startup: there is no // longer a programmatic way of setting these from JS. volatile uint32_t armHwCapFlags = HWCAP_UNINITIALIZED; bool CPUFlagsHaveBeenComputed() { return armHwCapFlags != HWCAP_UNINITIALIZED; } static const char* gArmHwCapString = nullptr; void SetARMHwCapFlagsString(const char* armHwCap) { MOZ_ASSERT(!CPUFlagsHaveBeenComputed()); gArmHwCapString = armHwCap; } static void ParseARMHwCapFlags(const char* armHwCap) { MOZ_ASSERT(armHwCap); if (strstr(armHwCap, "help")) { fflush(NULL); printf( "\n" "usage: ARMHWCAP=option,option,option,... where options can be:\n" "\n" " vfp \n" " neon \n" " vfpv3 \n" " vfpv3d16 \n" " vfpv4 \n" " idiva \n" " idivt \n" " vfpd32 \n" " armv7 \n" " align - unaligned accesses will trap and be emulated\n" #ifdef JS_SIMULATOR_ARM " nofixup - disable emulation of unaligned accesses\n" " hardfp \n" #endif "\n"); exit(0); /*NOTREACHED*/ } uint32_t flags = ParseARMCpuFeatures(armHwCap, /* override = */ true); #ifdef JS_CODEGEN_ARM_HARDFP flags |= HWCAP_USE_HARDFP_ABI; #endif armHwCapFlags = CanonicalizeARMHwCapFlags(flags); JitSpew(JitSpew_Codegen, "ARM HWCAP: 0x%x\n", armHwCapFlags); } void InitARMFlags() { MOZ_RELEASE_ASSERT(armHwCapFlags == HWCAP_UNINITIALIZED); if (const char* env = getenv("ARMHWCAP")) { ParseARMHwCapFlags(env); return; } if (gArmHwCapString) { ParseARMHwCapFlags(gArmHwCapString); return; } uint32_t flags = 0; #ifdef JS_SIMULATOR_ARM // HWCAP_FIXUP_FAULT is on by default even if HWCAP_ALIGNMENT_FAULT is // not on by default, because some memory access instructions always fault. // Notably, this is true for floating point accesses. flags = HWCAP_ARMv7 | HWCAP_VFP | HWCAP_VFPv3 | HWCAP_VFPv4 | HWCAP_NEON | HWCAP_IDIVA | HWCAP_FIXUP_FAULT; #else # if defined(__linux__) || defined(ANDROID) // This includes Android and B2G. bool readAuxv = false; int fd = open("/proc/self/auxv", O_RDONLY); if (fd > 0) { struct { uint32_t a_type; uint32_t a_val; } aux; while (read(fd, &aux, sizeof(aux))) { if (aux.a_type == AT_HWCAP) { flags = aux.a_val; readAuxv = true; break; } } close(fd); } FILE* fp = fopen("/proc/cpuinfo", "r"); if (fp) { char buf[1024] = {}; size_t len = fread(buf, sizeof(char), sizeof(buf) - 1, fp); fclose(fp); buf[len] = '\0'; // Read the cpuinfo Features if the auxv is not available. if (!readAuxv) { char* featureList = strstr(buf, "Features"); if (featureList) { if (char* featuresEnd = strstr(featureList, "\n")) { *featuresEnd = '\0'; } flags = ParseARMCpuFeatures(featureList + 8); } if (strstr(buf, "ARMv7")) { flags |= HWCAP_ARMv7; } } // The exynos7420 cpu (EU galaxy S6 (Note)) has a bug where sometimes // flushing doesn't invalidate the instruction cache. As a result we force // it by calling the cacheFlush twice on different start addresses. char* exynos7420 = strstr(buf, "Exynos7420"); if (exynos7420) { forceDoubleCacheFlush = true; } } # endif // If compiled to use specialized features then these features can be // assumed to be present otherwise the compiler would fail to run. # ifdef JS_CODEGEN_ARM_HARDFP // Compiled to use the hardfp ABI. flags |= HWCAP_USE_HARDFP_ABI; # endif # if defined(__VFP_FP__) && !defined(__SOFTFP__) // Compiled to use VFP instructions so assume VFP support. flags |= HWCAP_VFP; # endif # if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) // Compiled to use ARMv7 instructions so assume the ARMv7 arch. flags |= HWCAP_ARMv7; # endif # if defined(__APPLE__) # if defined(__ARM_NEON__) flags |= HWCAP_NEON; # endif # if defined(__ARMVFPV3__) flags |= HWCAP_VFPv3 | HWCAP_VFPD32 # endif # endif #endif // JS_SIMULATOR_ARM armHwCapFlags = CanonicalizeARMHwCapFlags(flags); JitSpew(JitSpew_Codegen, "ARM HWCAP: 0x%x\n", armHwCapFlags); return; } uint32_t GetARMFlags() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags; } bool HasNEON() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_NEON; } bool HasARMv7() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_ARMv7; } bool HasMOVWT() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_ARMv7; } bool HasLDSTREXBHD() { // These are really available from ARMv6K and later, but why bother? MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_ARMv7; } bool HasDMBDSBISB() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_ARMv7; } bool HasVFPv3() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_VFPv3; } bool HasVFP() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_VFP; } bool Has32DP() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_VFPD32; } bool HasIDIV() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_IDIVA; } // This is defined in the header and inlined when not using the simulator. #ifdef JS_SIMULATOR_ARM bool UseHardFpABI() { MOZ_ASSERT(armHwCapFlags != HWCAP_UNINITIALIZED); return armHwCapFlags & HWCAP_USE_HARDFP_ABI; } #endif Registers::Code Registers::FromName(const char* name) { // Check for some register aliases first. if (strcmp(name, "ip") == 0) { return ip; } if (strcmp(name, "r13") == 0) { return r13; } if (strcmp(name, "lr") == 0) { return lr; } if (strcmp(name, "r15") == 0) { return r15; } for (size_t i = 0; i < Total; i++) { if (strcmp(GetName(i), name) == 0) { return Code(i); } } return Invalid; } FloatRegisters::Code FloatRegisters::FromName(const char* name) { for (size_t i = 0; i < TotalSingle; ++i) { if (strcmp(GetSingleName(Encoding(i)), name) == 0) { return VFPRegister(i, VFPRegister::Single).code(); } } for (size_t i = 0; i < TotalDouble; ++i) { if (strcmp(GetDoubleName(Encoding(i)), name) == 0) { return VFPRegister(i, VFPRegister::Double).code(); } } return Invalid; } FloatRegisterSet VFPRegister::ReduceSetForPush(const FloatRegisterSet& s) { #ifdef ENABLE_WASM_SIMD # error "Needs more careful logic if SIMD is enabled" #endif LiveFloatRegisterSet mod; for (FloatRegisterIterator iter(s); iter.more(); ++iter) { if ((*iter).isSingle()) { // Add in just this float. mod.addUnchecked(*iter); } else if ((*iter).id() < 16) { // A double with an overlay, add in both floats. mod.addUnchecked((*iter).singleOverlay(0)); mod.addUnchecked((*iter).singleOverlay(1)); } else { // Add in the lone double in the range 16-31. mod.addUnchecked(*iter); } } return mod.set(); } uint32_t VFPRegister::GetPushSizeInBytes(const FloatRegisterSet& s) { #ifdef ENABLE_WASM_SIMD # error "Needs more careful logic if SIMD is enabled" #endif FloatRegisterSet ss = s.reduceSetForPush(); uint64_t bits = ss.bits(); uint32_t ret = mozilla::CountPopulation32(bits & 0xffffffff) * sizeof(float); ret += mozilla::CountPopulation32(bits >> 32) * sizeof(double); return ret; } uint32_t VFPRegister::getRegisterDumpOffsetInBytes() { #ifdef ENABLE_WASM_SIMD # error "Needs more careful logic if SIMD is enabled" #endif if (isSingle()) { return id() * sizeof(float); } if (isDouble()) { return id() * sizeof(double); } MOZ_CRASH("not Single or Double"); } uint32_t FloatRegisters::ActualTotalPhys() { if (Has32DP()) { return 32; } return 16; } void FlushICache(void* code, size_t size) { #if defined(JS_SIMULATOR_ARM) js::jit::SimulatorProcess::FlushICache(code, size); #elif (defined(__linux__) || defined(ANDROID)) && defined(__GNUC__) void* end = (void*)(reinterpret_cast(code) + size); asm volatile( "push {r7}\n" "mov r0, %0\n" "mov r1, %1\n" "mov r7, #0xf0000\n" "add r7, r7, #0x2\n" "mov r2, #0x0\n" "svc 0x0\n" "pop {r7}\n" : : "r"(code), "r"(end) : "r0", "r1", "r2"); if (forceDoubleCacheFlush) { void* start = (void*)((uintptr_t)code + 1); asm volatile( "push {r7}\n" "mov r0, %0\n" "mov r1, %1\n" "mov r7, #0xf0000\n" "add r7, r7, #0x2\n" "mov r2, #0x0\n" "svc 0x0\n" "pop {r7}\n" : : "r"(start), "r"(end) : "r0", "r1", "r2"); } #elif defined(__FreeBSD__) || defined(__NetBSD__) __clear_cache(code, reinterpret_cast(code) + size); #elif defined(XP_IOS) sys_icache_invalidate(code, size); #else # error "Unexpected platform" #endif } void FlushExecutionContext() { #ifndef JS_SIMULATOR_ARM // Ensure that any instructions already in the pipeline are discarded and // reloaded from the icache. asm volatile("isb\n" : : : "memory"); #else // We assume the icache flushing routines on other platforms take care of this #endif } } // namespace jit } // namespace js