/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.memory; import static jpcsp.util.Utilities.round4; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.Arrays; import jpcsp.Emulator; import jpcsp.Memory; import jpcsp.MemoryMap; import jpcsp.HLE.Modules; public class FastMemory extends Memory { // // In a typical application, the following read/write operations are performed: // - read8 : 1,45% of total read/write, 1,54% of total read operations // - read16 : 13,90% of total read/write, 14,76% of total read operations // - read32 : 78,80% of total read/write, 83,70% of total read operations // - write8 : 1,81% of total read/write, 30,96% of total write operations // - write16: 0,02% of total read/write, 0,38% of total write operations // - write32: 4,02% of total read/write, 68,67% of total write operations // // This is why this Memory implementation is optimized for fast read32 operations. // Drawback is the higher memory requirements. // // This implementation is performing very few checks for the validity of // memory address references to achieve the highest performance. // Use SafeFastMemory for complete address checks. // private int[] all; // Enable/disable read & write tracing. // Use final variables to not reduce the performance // (the code is removed/inserted at Java compile time) private static final boolean traceRead = false; private static final boolean traceWrite = false; // Array containing only 0, for fast memset(addr, 0, length); public static final int[] zero = new int[32768]; private static final int[] memory8Shift = { 0, 8, 16, 24 }; private static final int[] memory8Mask = { 0xFFFFFF00, 0xFFFF00FF, 0xFF00FFFF, 0x00FFFFFF }; private static final int[] memory16Shift = { 0, 0, 16, 16 }; private static final int[] memory16Mask = { 0xFFFF0000, 0xFFFF0000, 0x0000FFFF, 0x0000FFFF }; private static final boolean[] isIntAligned = { true, false, false, false }; @Override public boolean allocate() { // Free previously allocated memory all = null; int allSize = (MemoryMap.END_RAM + 1) >> 2; try { all = new int[allSize]; } catch (OutOfMemoryError e) { // Not enough memory provided for this VM, cannot use FastMemory model Memory.log.warn("Cannot allocate FastMemory: add the option '-Xmx256m' to the Java Virtual Machine startup command to improve Performance"); Memory.log.info("The current Java Virtual Machine has been started using '-Xmx" + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + "m'"); return false; } return super.allocate(); } @Override public void Initialise() { Arrays.fill(zero, 0); Arrays.fill(all, 0); } @Override public int read8(int address) { address &= addressMask; int data = (all[address >> 2] >> memory8Shift[address & 0x03]) & 0xFF; if (traceRead) { if (log.isTraceEnabled()) { log.trace(String.format("read8(0x%08X)=0x%02X", address, data)); } } return data; } @Override public int read16(int address) { address &= addressMask; int data = (all[address >> 2] >> memory16Shift[address & 0x02]) & 0xFFFF; if (traceRead) { if (log.isTraceEnabled()) { log.trace(String.format("read16(0x%08X)=0x%04X", address, data)); } } return data; } @Override public int read32(int address) { try { address &= addressMask; int data = all[address >> 2]; if (traceRead) { if (log.isTraceEnabled()) { log.trace(String.format("read32(0x%08X)=0x%08X (%f)", address, data, Float.intBitsToFloat(data))); } } return data; } catch (ArrayIndexOutOfBoundsException e) { if (read32AllowedInvalidAddress(address)) { return 0; } invalidMemoryAddress(address, "read32", Emulator.EMU_STATUS_MEM_READ); return 0; } } @Override public long read64(int address) { address &= addressMask; long data = (((long) all[(address >> 2) + 1]) << 32) | (((long) all[address >> 2]) & 0xFFFFFFFFL); if (traceRead) { if (log.isTraceEnabled()) { log.trace(String.format("read64(0x%08X)=0x%X", address, data)); } } return data; } @Override public void write8(int address, byte data) { address &= addressMask; int index = address & 0x03; int memData = (all[address >> 2] & memory8Mask[index]) | ((data & 0xFF) << memory8Shift[index]); if (traceWrite) { if (log.isTraceEnabled()) { log.trace(String.format("write8(0x%08X, 0x%02X)", address, data & 0xFF)); } } all[address >> 2] = memData; Modules.sceDisplayModule.write8(address); } @Override public void write16(int address, short data) { address &= addressMask; int index = address & 0x02; int memData = (all[address >> 2] & memory16Mask[index]) | ((data & 0xFFFF) << memory16Shift[index]); if (traceWrite) { if (log.isTraceEnabled()) { log.trace(String.format("write16(0x%08X, 0x%04X)", address, data & 0xFFFF)); } } all[address >> 2] = memData; Modules.sceDisplayModule.write16(address); } @Override public void write32(int address, int data) { address &= addressMask; if (traceWrite) { if (log.isTraceEnabled()) { log.trace(String.format("write32(0x%08X, %08X (%f))", address, data, Float.intBitsToFloat(data))); } } all[address >> 2] = data; Modules.sceDisplayModule.write32(address); } @Override public void write64(int address, long data) { address &= addressMask; if (traceWrite) { if (log.isTraceEnabled()) { log.trace(String.format("write64(0x%08X, 0x%X", address, data)); } } all[address >> 2] = (int) data; all[(address >> 2) + 1] = (int) (data >> 32); } @Override public IntBuffer getMainMemoryByteBuffer() { return IntBuffer.wrap(all, MemoryMap.START_RAM >> 2, MemoryMap.SIZE_RAM >> 2); } @Override public IntBuffer getBuffer(int address, int length) { address = normalizeAddress(address); IntBuffer buffer = getMainMemoryByteBuffer(); buffer.position(address >> 2); buffer.limit(round4(round4(address) + length) >> 2); return buffer.slice(); } private static boolean isIntAligned(int n) { return isIntAligned[n & 0x03]; } @Override public void memset(int address, byte data, int length) { address = normalizeAddress(address); Modules.sceDisplayModule.write(address); for (; !isIntAligned(address) && length > 0; address++, length--) { write8(address, data); } int count4 = length >> 2; if (count4 > 0) { if (data == 0) { // Fast memset(addr, 0, length) using copy from "zero" array for (int i = 0; i < count4; i += zero.length) { System.arraycopy(zero, 0, all, (address >> 2) + i, Math.min(zero.length, count4 - i)); } } else { int data1 = data & 0xFF; int data4 = (data1 << 24) | (data1 << 16) | (data1 << 8) | data1; Arrays.fill(all, address >> 2, (address >> 2) + count4, data4); } address += count4 << 2; length -= count4 << 2; } for (; length > 0; address++, length--) { write8(address, data); } } @Override public void copyToMemory(int address, ByteBuffer source, int length) { // copy in 1 byte steps until address is "int"-aligned while (!isIntAligned(address) && length > 0 && source.hasRemaining()) { byte b = source.get(); write8(address, b); address++; length--; } // copy 1 int at each loop int countInt = Math.min(length, source.remaining()) >> 2; IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(address, countInt << 2, 4); for (int i = 0; i < countInt; i++) { int data1 = source.get() & 0xFF; int data2 = source.get() & 0xFF; int data3 = source.get() & 0xFF; int data4 = source.get() & 0xFF; int data = (data4 << 24) | (data3 << 16) | (data2 << 8) | data1; memoryWriter.writeNext(data); } memoryWriter.flush(); int copyLength = countInt << 2; length -= copyLength; address += copyLength; // copy rest length in 1 byte steps (rest length <= 3) while (length > 0 && source.hasRemaining()) { byte b = source.get(); write8(address, b); address++; length--; } } public int[] getAll() { return all; } // Source, destination and length are "int"-aligned private void memcpyAligned4(int destination, int source, int length, boolean checkOverlap) { if (checkOverlap || !areOverlapping(destination, source, length)) { // Direct copy, System.arraycopy is handling correctly overlapping arrays System.arraycopy(all, source >> 2, all, destination >> 2, length >> 2); } else { // Buffers are overlapping, but we have to copy as they would not overlap. // Unfortunately, IntBuffer operation are always checking for overlapping buffers, // so we have to copy manually... int src = source >> 2; int dst = destination >> 2; for (int i = 0; i < length; i += 4) { all[dst++] = all[src++]; } } } @Override protected void memcpy(int destination, int source, int length, boolean checkOverlap) { if (length <= 0) { return; } destination = normalizeAddress(destination); source = normalizeAddress(source); Modules.sceDisplayModule.write(destination); if (isIntAligned(source) && isIntAligned(destination) && isIntAligned(length)) { // Source, destination and length are "int"-aligned memcpyAligned4(destination, source, length, checkOverlap); } else if ((source & 0x03) == (destination & 0x03) && (!checkOverlap || !areOverlapping(destination, source, length))) { // Source and destination have the same alignment and are not overlapping while (!isIntAligned(source) && length > 0) { write8(destination, (byte) read8(source)); source++; destination++; length--; } int length4 = length & ~0x03; if (length4 > 0) { memcpyAligned4(destination, source, length4, checkOverlap); source += length4; destination += length4; length -= length4; } while (length > 0) { write8(destination, (byte) read8(source)); destination++; source++; length--; } } else { // // Buffers are not "int"-aligned, copy in 1 byte steps. // Overlapping address ranges must be correctly handled: // If source >= destination: // [---source---] // [---destination---] // => Copy from the head // If source < destination: // [---source---] // [---destination---] // => Copy from the tail // if (!checkOverlap || source >= destination || !areOverlapping(destination, source, length)) { if (areOverlapping(destination, source, 4)) { // Cannot use MemoryReader if source and destination are overlapping in less than 4 bytes for (int i = 0; i < length; i++) { write8(destination + i, (byte) read8(source + i)); } } else { IMemoryReader sourceReader = MemoryReader.getMemoryReader(source, length, 1); IMemoryWriter destinationWriter = MemoryWriter.getMemoryWriter(destination, length, 1); for (int i = 0; i < length; i++) { destinationWriter.writeNext(sourceReader.readNext()); } destinationWriter.flush(); } } else { for (int i = length - 1; i >= 0; i--) { write8(destination + i, (byte) read8(source + i)); } } } } }