diff options
| author | David 'Digit' Turner <digit@google.com> | 2014-03-11 19:58:51 +0000 |
|---|---|---|
| committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2014-03-11 19:58:51 +0000 |
| commit | 84b68033f6b92a3c35ac1d3ab7f8278c54744cff (patch) | |
| tree | ba6e795703570bd83fbfcdf92216359452ebb358 /emulator/opengl/shared | |
| parent | 81431aae3aaf95209a606511b3a81f6aa06ffe9f (diff) | |
| parent | 56b89bc863d1a5da6234f05923f63b5466e1ce3f (diff) | |
| download | sdk-84b68033f6b92a3c35ac1d3ab7f8278c54744cff.zip sdk-84b68033f6b92a3c35ac1d3ab7f8278c54744cff.tar.gz sdk-84b68033f6b92a3c35ac1d3ab7f8278c54744cff.tar.bz2 | |
Merge changes I205392bd,I84a058bb,I22c67f11,Id18319e8,I9836ed96, ... into idea133
* changes:
emulator/opengl: Allow standalone build.
emulator/opengl: Remove libcutils/libutils/liblog
emulator/opengl: Remove <utils/List.h> + <utils/String8.h>
emulator/opengl: Remove android::KeyedVector usage.
emulator/opengl: Remove android::Vector<> usage.
emulator/opengl: Remove <cutils/sockets.h>
Diffstat (limited to 'emulator/opengl/shared')
18 files changed, 1573 insertions, 251 deletions
diff --git a/emulator/opengl/shared/OpenglCodecCommon/Android.mk b/emulator/opengl/shared/OpenglCodecCommon/Android.mk index 5deb1f7..b6a7e07 100644 --- a/emulator/opengl/shared/OpenglCodecCommon/Android.mk +++ b/emulator/opengl/shared/OpenglCodecCommon/Android.mk @@ -24,19 +24,21 @@ endif $(call emugl-begin-host-static-library,libOpenglCodecCommon) LOCAL_SRC_FILES := $(host_commonSources) - -$(call emugl-export,STATIC_LIBRARIES,libcutils) -$(call emugl-export,C_INCLUDES,$(LOCAL_PATH)) +$(call emugl-import, libemugl_common) +$(call emugl-export,C_INCLUDES,$(EMUGL_PATH)/host/include/libOpenglRender $(LOCAL_PATH)) +$(call emugl-export,LDLIBS,-lstdc++) $(call emugl-end-module) ### OpenglCodecCommon host, 64-bit ######################################### -$(call emugl-begin-host-static-library,lib64OpenglCodecCommon) +ifdef EMUGL_BUILD_64BITS + $(call emugl-begin-host-static-library,lib64OpenglCodecCommon) -LOCAL_SRC_FILES := $(host_commonSources) - -$(call emugl-export,STATIC_LIBRARIES,lib64cutils) -$(call emugl-export,C_INCLUDES,$(LOCAL_PATH)) -$(call emugl-export,CFLAGS,-m64) -$(call emugl-end-module) + LOCAL_SRC_FILES := $(host_commonSources) + $(call emugl-import, lib64emugl_common) + $(call emugl-export,C_INCLUDES,$(EMUGL_PATH)/host/include/libOpenglRender $(LOCAL_PATH)) + $(call emugl-export,CFLAGS,-m64 -fPIC) + $(call emugl-export,LDLIBS,-lstdc++) + $(call emugl-end-module) +endif diff --git a/emulator/opengl/shared/OpenglCodecCommon/ErrorLog.h b/emulator/opengl/shared/OpenglCodecCommon/ErrorLog.h index 43577d4..4cad61f 100644 --- a/emulator/opengl/shared/OpenglCodecCommon/ErrorLog.h +++ b/emulator/opengl/shared/OpenglCodecCommon/ErrorLog.h @@ -16,22 +16,12 @@ #ifndef _ERROR_LOG_H_ #define _ERROR_LOG_H_ -#ifdef __ANDROID__ -# include <cutils/log.h> -# define ERR(...) ALOGE(__VA_ARGS__) -# ifdef EMUGL_DEBUG -# define DBG(...) ALOGD(__VA_ARGS__) -# else -# define DBG(...) ((void)0) -# endif +#include <stdio.h> +#define ERR(...) fprintf(stderr, __VA_ARGS__) +#ifdef EMUGL_DEBUG +# define DBG(...) fprintf(stderr, __VA_ARGS__) #else -# include <stdio.h> -# define ERR(...) fprintf(stderr, __VA_ARGS__) -# ifdef EMUGL_DEBUG -# define DBG(...) fprintf(stderr, __VA_ARGS__) -# else -# define DBG(...) ((void)0) -# endif +# define DBG(...) ((void)0) #endif -#endif +#endif // _ERROR_LOG_H_ diff --git a/emulator/opengl/shared/OpenglCodecCommon/GLSharedGroup.cpp b/emulator/opengl/shared/OpenglCodecCommon/GLSharedGroup.cpp index a054562..db7f418 100644 --- a/emulator/opengl/shared/OpenglCodecCommon/GLSharedGroup.cpp +++ b/emulator/opengl/shared/OpenglCodecCommon/GLSharedGroup.cpp @@ -16,23 +16,22 @@ #include "GLSharedGroup.h" -/**** KeyedVector utilities ****/ - -template <typename T> -static void clearObjectMap(android::DefaultKeyedVector<GLuint, T>& v) { - for (size_t i = 0; i < v.size(); i++) - delete v.valueAt(i); - v.clear(); -} +#include <string.h> /**** BufferData ****/ BufferData::BufferData() : m_size(0) {}; + BufferData::BufferData(GLsizeiptr size, void * data) : m_size(size) { - void * buffer = NULL; - if (size>0) buffer = m_fixedBuffer.alloc(size); - if (data) memcpy(buffer, data, size); + void* buffer = NULL; + + if (size > 0) { + buffer = m_fixedBuffer.alloc(size); + if (data) { + memcpy(buffer, data, size); + } + } } /**** ProgramData ****/ @@ -204,9 +203,7 @@ bool ProgramData::attachShader(GLuint shader) return false; } } - // AKA m_shaders.push_back(), but that has an ambiguous call to insertAt() - // due to the default parameters. This is the desired insertAt() overload. - m_shaders.insertAt(shader, m_shaders.size(), 1); + m_shaders.append(shader); return true; } @@ -215,7 +212,7 @@ bool ProgramData::detachShader(GLuint shader) size_t n = m_shaders.size(); for (size_t i = 0; i < n; i++) { if (m_shaders[i] == shader) { - m_shaders.removeAt(i); + m_shaders.remove(i); return true; } } @@ -225,49 +222,32 @@ bool ProgramData::detachShader(GLuint shader) /***** GLSharedGroup ****/ GLSharedGroup::GLSharedGroup() : - m_buffers(android::DefaultKeyedVector<GLuint, BufferData*>(NULL)), - m_programs(android::DefaultKeyedVector<GLuint, ProgramData*>(NULL)), - m_shaders(android::DefaultKeyedVector<GLuint, ShaderData*>(NULL)) -{ -} + m_buffers(), m_programs(), m_shaders() {} -GLSharedGroup::~GLSharedGroup() -{ - m_buffers.clear(); - m_programs.clear(); - clearObjectMap(m_buffers); - clearObjectMap(m_programs); - clearObjectMap(m_shaders); -} +GLSharedGroup::~GLSharedGroup() {} BufferData * GLSharedGroup::getBufferData(GLuint bufferId) { emugl::Mutex::AutoLock _lock(m_lock); - return m_buffers.valueFor(bufferId); + return m_buffers.get(bufferId); } void GLSharedGroup::addBufferData(GLuint bufferId, GLsizeiptr size, void * data) { emugl::Mutex::AutoLock _lock(m_lock); - m_buffers.add(bufferId, new BufferData(size, data)); + m_buffers.set(bufferId, new BufferData(size, data)); } void GLSharedGroup::updateBufferData(GLuint bufferId, GLsizeiptr size, void * data) { emugl::Mutex::AutoLock _lock(m_lock); - ssize_t idx = m_buffers.indexOfKey(bufferId); - if (idx >= 0) { - delete m_buffers.valueAt(idx); - m_buffers.editValueAt(idx) = new BufferData(size, data); - } else { - m_buffers.add(bufferId, new BufferData(size, data)); - } + m_buffers.set(bufferId, new BufferData(size, data)); } GLenum GLSharedGroup::subUpdateBufferData(GLuint bufferId, GLintptr offset, GLsizeiptr size, void * data) { emugl::Mutex::AutoLock _lock(m_lock); - BufferData * buf = m_buffers.valueFor(bufferId); + BufferData * buf = m_buffers.get(bufferId); if ((!buf) || (buf->m_size < offset+size) || (offset < 0) || (size<0)) return GL_INVALID_VALUE; //it's safe to update now @@ -278,32 +258,20 @@ GLenum GLSharedGroup::subUpdateBufferData(GLuint bufferId, GLintptr offset, GLsi void GLSharedGroup::deleteBufferData(GLuint bufferId) { emugl::Mutex::AutoLock _lock(m_lock); - ssize_t idx = m_buffers.indexOfKey(bufferId); - if (idx >= 0) { - delete m_buffers.valueAt(idx); - m_buffers.removeItemsAt(idx); - } + ssize_t idx = m_buffers.remove(bufferId); } void GLSharedGroup::addProgramData(GLuint program) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData *pData = m_programs.valueFor(program); - if (pData) - { - m_programs.removeItem(program); - delete pData; - } - - m_programs.add(program,new ProgramData()); + m_programs.set(program, new ProgramData()); } void GLSharedGroup::initProgramData(GLuint program, GLuint numIndexes) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData *pData = m_programs.valueFor(program); - if (pData) - { + ProgramData *pData = m_programs.get(program); + if (pData) { pData->initProgramData(numIndexes); } } @@ -311,136 +279,135 @@ void GLSharedGroup::initProgramData(GLuint program, GLuint numIndexes) bool GLSharedGroup::isProgramInitialized(GLuint program) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); - if (pData) - { - return pData->isInitialized(); - } - return false; + ProgramData* pData = m_programs.get(program); + return pData && pData->isInitialized(); } void GLSharedGroup::deleteProgramData(GLuint program) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData *pData = m_programs.valueFor(program); - if (pData) - delete pData; - m_programs.removeItem(program); + m_programs.remove(program); } void GLSharedGroup::attachShader(GLuint program, GLuint shader) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* programData = m_programs.valueFor(program); - ssize_t idx = m_shaders.indexOfKey(shader); - if (programData && idx >= 0) { - if (programData->attachShader(shader)) { - refShaderDataLocked(idx); - } + ProgramData* programData = m_programs.get(program); + if (programData && programData->attachShader(shader)) { + refShaderDataLocked(shader); } } void GLSharedGroup::detachShader(GLuint program, GLuint shader) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* programData = m_programs.valueFor(program); - ssize_t idx = m_shaders.indexOfKey(shader); - if (programData && idx >= 0) { - if (programData->detachShader(shader)) { - unrefShaderDataLocked(idx); - } + ProgramData* programData = m_programs.get(program); + if (programData && programData->detachShader(shader)) { + unrefShaderDataLocked(shader); } } -void GLSharedGroup::setProgramIndexInfo(GLuint program, GLuint index, GLint base, GLint size, GLenum type, const char* name) +void GLSharedGroup::setProgramIndexInfo(GLuint program, + GLuint index, + GLint base, + GLint size, + GLenum type, + const char* name) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); - if (pData) - { - pData->setIndexInfo(index,base,size,type); - - if (type == GL_SAMPLER_2D) { - size_t n = pData->getNumShaders(); - for (size_t i = 0; i < n; i++) { - GLuint shaderId = pData->getShader(i); - ShaderData* shader = m_shaders.valueFor(shaderId); - if (!shader) continue; - ShaderData::StringList::iterator nameIter = shader->samplerExternalNames.begin(); - ShaderData::StringList::iterator nameEnd = shader->samplerExternalNames.end(); - while (nameIter != nameEnd) { - if (*nameIter == name) { - pData->setIndexFlags(index, ProgramData::INDEX_FLAG_SAMPLER_EXTERNAL); - break; - } - ++nameIter; + ProgramData* pData = m_programs.get(program); + if (!pData) { + return; + } + pData->setIndexInfo(index,base,size,type); + + if (type == GL_SAMPLER_2D) { + size_t n = pData->getNumShaders(); + for (size_t i = 0; i < n; i++) { + GLuint shaderId = pData->getShader(i); + ShaderData* shader = m_shaders.get(shaderId); + if (!shader) continue; +#if 0 // TODO(digit): Understand why samplerExternalNames is always empty? + ShaderData::StringList::iterator nameIter = + shader->samplerExternalNames.begin(); + ShaderData::StringList::iterator nameEnd = + shader->samplerExternalNames.end(); + while (nameIter != nameEnd) { + if (*nameIter == name) { + pData->setIndexFlags( + index, + ProgramData::INDEX_FLAG_SAMPLER_EXTERNAL); + break; } + ++nameIter; } +#endif } } } + GLenum GLSharedGroup::getProgramUniformType(GLuint program, GLint location) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); - GLenum type=0; - if (pData) - { - type = pData->getTypeForLocation(location); - } - return type; + ProgramData* pData = m_programs.get(program); + return pData ? pData->getTypeForLocation(location) : 0; } bool GLSharedGroup::isProgram(GLuint program) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); - return (pData!=NULL); + ProgramData* pData = m_programs.get(program); + return (pData != NULL); } void GLSharedGroup::setupLocationShiftWAR(GLuint program) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); + ProgramData* pData = m_programs.get(program); if (pData) pData->setupLocationShiftWAR(); } -GLint GLSharedGroup::locationWARHostToApp(GLuint program, GLint hostLoc, GLint arrIndex) +GLint GLSharedGroup::locationWARHostToApp(GLuint program, + GLint hostLoc, + GLint arrIndex) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); - if (pData) return pData->locationWARHostToApp(hostLoc, arrIndex); - else return hostLoc; + ProgramData* pData = m_programs.get(program); + return pData ? pData->locationWARHostToApp(hostLoc, arrIndex) : hostLoc; } GLint GLSharedGroup::locationWARAppToHost(GLuint program, GLint appLoc) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); - if (pData) return pData->locationWARAppToHost(appLoc); - else return appLoc; + ProgramData* pData = m_programs.get(program); + return pData ? pData->locationWARAppToHost(appLoc) : appLoc; } bool GLSharedGroup::needUniformLocationWAR(GLuint program) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); - if (pData) return pData->needUniformLocationWAR(); - return false; + ProgramData* pData = m_programs.get(program); + return pData ? pData->needUniformLocationWAR() : false; } -GLint GLSharedGroup::getNextSamplerUniform(GLuint program, GLint index, GLint* val, GLenum* target) const +GLint GLSharedGroup::getNextSamplerUniform(GLuint program, + GLint index, + GLint* val, + GLenum* target) const { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); + ProgramData* pData = m_programs.get(program); return pData ? pData->getNextSamplerUniform(index, val, target) : -1; } -bool GLSharedGroup::setSamplerUniform(GLuint program, GLint appLoc, GLint val, GLenum* target) +bool GLSharedGroup::setSamplerUniform(GLuint program, + GLint appLoc, + GLint val, + GLenum* target) { emugl::Mutex::AutoLock _lock(m_lock); - ProgramData* pData = m_programs.valueFor(program); + ProgramData* pData = m_programs.get(program); return pData ? pData->setSamplerUniform(appLoc, val, target) : false; } @@ -448,44 +415,39 @@ bool GLSharedGroup::addShaderData(GLuint shader) { emugl::Mutex::AutoLock _lock(m_lock); ShaderData* data = new ShaderData; - if (data) { - if (m_shaders.add(shader, data) < 0) { - delete data; - data = NULL; - } - data->refcount = 1; - } - return data != NULL; + data->refcount = 1; + m_shaders.set(shader, data); + return true; } ShaderData* GLSharedGroup::getShaderData(GLuint shader) { emugl::Mutex::AutoLock _lock(m_lock); - return m_shaders.valueFor(shader); + ShaderData* data = m_shaders.get(shader); + if (data) { + data->refcount++; + } + return data; } void GLSharedGroup::unrefShaderData(GLuint shader) { emugl::Mutex::AutoLock _lock(m_lock); - ssize_t idx = m_shaders.indexOfKey(shader); - if (idx >= 0) { - unrefShaderDataLocked(idx); - } + unrefShaderDataLocked(shader); } -void GLSharedGroup::refShaderDataLocked(ssize_t shaderIdx) +void GLSharedGroup::refShaderDataLocked(GLuint shader) { - assert(shaderIdx >= 0 && shaderIdx <= m_shaders.size()); - ShaderData* data = m_shaders.valueAt(shaderIdx); - data->refcount++; + ShaderData* data = m_shaders.get(shader); + if (data) { + data->refcount++; + } } -void GLSharedGroup::unrefShaderDataLocked(ssize_t shaderIdx) +void GLSharedGroup::unrefShaderDataLocked(GLuint shader) { - assert(shaderIdx >= 0 && shaderIdx <= m_shaders.size()); - ShaderData* data = m_shaders.valueAt(shaderIdx); - if (--data->refcount == 0) { - delete data; - m_shaders.removeItemsAt(shaderIdx); + ShaderData* data = m_shaders.get(shader); + if (data && --data->refcount == 0) { + m_shaders.remove(shader); } } diff --git a/emulator/opengl/shared/OpenglCodecCommon/GLSharedGroup.h b/emulator/opengl/shared/OpenglCodecCommon/GLSharedGroup.h index 02278a2..f111f99 100644 --- a/emulator/opengl/shared/OpenglCodecCommon/GLSharedGroup.h +++ b/emulator/opengl/shared/OpenglCodecCommon/GLSharedGroup.h @@ -16,6 +16,11 @@ #ifndef _GL_SHARED_GROUP_H_ #define _GL_SHARED_GROUP_H_ +#include "emugl/common/id_to_object_map.h" +#include "emugl/common/mutex.h" +#include "emugl/common/pod_vector.h" +#include "emugl/common/smart_ptr.h" + #define GL_API #ifndef ANDROID #define GL_APIENTRY @@ -30,18 +35,13 @@ #include <stdio.h> #include <stdlib.h> #include "ErrorLog.h" -#include <utils/KeyedVector.h> -#include <utils/List.h> -#include <utils/String8.h> #include "FixedBuffer.h" -#include "emugl/common/mutex.h" -#include "emugl/common/smart_ptr.h" struct BufferData { BufferData(); BufferData(GLsizeiptr size, void * data); GLsizeiptr m_size; - FixedBuffer m_fixedBuffer; + FixedBuffer m_fixedBuffer; }; class ProgramData { @@ -61,7 +61,7 @@ private: bool m_initialized; bool m_locShiftWAR; - android::Vector<GLuint> m_shaders; + emugl::PodVector<GLuint> m_shaders; public: enum { @@ -92,20 +92,22 @@ public: }; struct ShaderData { +#if 0 // TODO(digit): Undertand why this is never used? typedef android::List<android::String8> StringList; StringList samplerExternalNames; +#endif int refcount; }; class GLSharedGroup { private: - android::DefaultKeyedVector<GLuint, BufferData*> m_buffers; - android::DefaultKeyedVector<GLuint, ProgramData*> m_programs; - android::DefaultKeyedVector<GLuint, ShaderData*> m_shaders; + emugl::IdToObjectMap<BufferData> m_buffers; + emugl::IdToObjectMap<ProgramData> m_programs; + emugl::IdToObjectMap<ShaderData> m_shaders; mutable emugl::Mutex m_lock; - void refShaderDataLocked(ssize_t shaderIdx); - void unrefShaderDataLocked(ssize_t shaderIdx); + void refShaderDataLocked(GLuint shader); + void unrefShaderDataLocked(GLuint shader); public: GLSharedGroup(); diff --git a/emulator/opengl/shared/OpenglCodecCommon/SocketStream.cpp b/emulator/opengl/shared/OpenglCodecCommon/SocketStream.cpp index f7a2314..3ef4c6f 100644 --- a/emulator/opengl/shared/OpenglCodecCommon/SocketStream.cpp +++ b/emulator/opengl/shared/OpenglCodecCommon/SocketStream.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ #include "SocketStream.h" -#include <cutils/sockets.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> @@ -53,6 +52,7 @@ SocketStream::~SocketStream() #else ::close(m_sock); #endif + m_sock = -1; } if (m_buf != NULL) { free(m_buf); @@ -140,7 +140,7 @@ const unsigned char *SocketStream::read( void *buf, size_t *inout_len) int n; do { - n = recv(buf, *inout_len); + n = this->recv(buf, *inout_len); } while( n < 0 && errno == EINTR ); if (n > 0) { diff --git a/emulator/opengl/shared/OpenglCodecCommon/TcpStream.cpp b/emulator/opengl/shared/OpenglCodecCommon/TcpStream.cpp index 8a6e56e..ba355ab 100644 --- a/emulator/opengl/shared/OpenglCodecCommon/TcpStream.cpp +++ b/emulator/opengl/shared/OpenglCodecCommon/TcpStream.cpp @@ -14,7 +14,8 @@ * limitations under the License. */ #include "TcpStream.h" -#include <cutils/sockets.h> +#include "emugl/common/sockets.h" + #include <errno.h> #include <stdio.h> #include <stdlib.h> @@ -30,77 +31,48 @@ #define LISTEN_BACKLOG 4 -TcpStream::TcpStream(size_t bufSize) : - SocketStream(bufSize) -{ -} +TcpStream::TcpStream(size_t bufSize) : SocketStream(bufSize) {} TcpStream::TcpStream(int sock, size_t bufSize) : - SocketStream(sock, bufSize) -{ + SocketStream(sock, bufSize) { // disable Nagle algorithm to improve bandwidth of small // packets which are quite common in our implementation. -#ifdef _WIN32 - DWORD flag; -#else - int flag; -#endif - flag = 1; - setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&flag, sizeof(flag) ); + emugl::socketTcpDisableNagle(sock); } -int TcpStream::listen(char addrstr[MAX_ADDRSTR_LEN]) -{ - m_sock = socket_loopback_server(0, SOCK_STREAM); +int TcpStream::listen(char addrstr[MAX_ADDRSTR_LEN]) { + m_sock = emugl::socketTcpLoopbackServer(0, SOCK_STREAM); if (!valid()) return int(ERR_INVALID_SOCKET); - /* get the actual port number assigned by the system */ - struct sockaddr_in addr; - socklen_t addrLen = sizeof(addr); - memset(&addr, 0, sizeof(addr)); - if (getsockname(m_sock, (struct sockaddr*)&addr, &addrLen) < 0) { - close(m_sock); + int port = emugl::socketGetPort(m_sock); + if (port < 0) { + ::close(m_sock); return int(ERR_INVALID_SOCKET); } - snprintf(addrstr, MAX_ADDRSTR_LEN - 1, "%hu", ntohs(addr.sin_port)); + + snprintf(addrstr, MAX_ADDRSTR_LEN - 1, "%hu", port); addrstr[MAX_ADDRSTR_LEN-1] = '\0'; return 0; } -SocketStream * TcpStream::accept() -{ - int clientSock = -1; - - while (true) { - struct sockaddr_in addr; - socklen_t len = sizeof(addr); - clientSock = ::accept(m_sock, (sockaddr *)&addr, &len); - - if (clientSock < 0 && errno == EINTR) { - continue; - } - break; - } +SocketStream * TcpStream::accept() { + int clientSock = emugl::socketAccept(m_sock); + if (clientSock < 0) + return NULL; - TcpStream *clientStream = NULL; - - if (clientSock >= 0) { - clientStream = new TcpStream(clientSock, m_bufsize); - } - return clientStream; + return new TcpStream(clientSock, m_bufsize); } -int TcpStream::connect(const char* addr) -{ +int TcpStream::connect(const char* addr) { int port = atoi(addr); - return connect("127.0.0.1",port); + m_sock = emugl::socketTcpLoopbackClient(port, SOCK_STREAM); + return valid() ? 0 : -1; } int TcpStream::connect(const char* hostname, unsigned short port) { - m_sock = socket_network_client(hostname, port, SOCK_STREAM); - if (!valid()) return -1; - return 0; + m_sock = emugl::socketTcpClient(hostname, port, SOCK_STREAM); + return valid() ? 0 : -1; } diff --git a/emulator/opengl/shared/OpenglCodecCommon/UnixStream.cpp b/emulator/opengl/shared/OpenglCodecCommon/UnixStream.cpp index b2eef6d..7b2f67d 100644 --- a/emulator/opengl/shared/OpenglCodecCommon/UnixStream.cpp +++ b/emulator/opengl/shared/OpenglCodecCommon/UnixStream.cpp @@ -14,7 +14,9 @@ * limitations under the License. */ #include "UnixStream.h" -#include <cutils/sockets.h> + +#include "emugl/common/sockets.h" + #include <errno.h> #include <stdio.h> #include <stdlib.h> @@ -92,7 +94,7 @@ int UnixStream::listen(char addrstr[MAX_ADDRSTR_LEN]) return -1; } - m_sock = socket_local_server(addrstr, ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); + m_sock = emugl::socketLocalServer(addrstr, SOCK_STREAM); if (!valid()) return int(ERR_INVALID_SOCKET); return 0; @@ -123,7 +125,7 @@ SocketStream * UnixStream::accept() int UnixStream::connect(const char* addr) { - m_sock = socket_local_client(addr, ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); + m_sock = emugl::socketLocalClient(addr, SOCK_STREAM); if (!valid()) return -1; return 0; diff --git a/emulator/opengl/shared/OpenglOsUtils/Android.mk b/emulator/opengl/shared/OpenglOsUtils/Android.mk index d8807d0..566fd10 100644 --- a/emulator/opengl/shared/OpenglOsUtils/Android.mk +++ b/emulator/opengl/shared/OpenglOsUtils/Android.mk @@ -37,10 +37,11 @@ $(call emugl-begin-host-static-library,libOpenglOsUtils) $(call emugl-end-module) ### 64-bit host library #### -$(call emugl-begin-host-static-library,lib64OpenglOsUtils) - $(call emugl-export,C_INCLUDES,$(host_common_INCLUDES)) - LOCAL_SRC_FILES = $(host_common_SRC_FILES) - $(call emugl-export,LDLIBS,$(host_common_LDLIBS)) - $(call emugl-export,CFLAGS,-m64) - $(call emugl-import,lib64emugl_common) -$(call emugl-end-module) +ifdef EMUGL_BUILD_64BITS + $(call emugl-begin-host-static-library,lib64OpenglOsUtils) + $(call emugl-export,C_INCLUDES,$(host_common_INCLUDES)) + LOCAL_SRC_FILES = $(host_common_SRC_FILES) + $(call emugl-export,LDLIBS,$(host_common_LDLIBS)) + $(call emugl-export,CFLAGS,-m64 -fPIC) + $(call emugl-end-module) +endif
\ No newline at end of file diff --git a/emulator/opengl/shared/emugl/common/Android.mk b/emulator/opengl/shared/emugl/common/Android.mk index f1c20b5..fd7761e 100644 --- a/emulator/opengl/shared/emugl/common/Android.mk +++ b/emulator/opengl/shared/emugl/common/Android.mk @@ -6,26 +6,37 @@ LOCAL_PATH := $(call my-dir) ### emugl_common host library ########################################### commonSources := \ + id_to_object_map.cpp \ lazy_instance.cpp \ + pod_vector.cpp \ smart_ptr.cpp \ + sockets.cpp \ thread_store.cpp \ host_commonSources := $(commonSources) $(call emugl-begin-host-static-library,libemugl_common) LOCAL_SRC_FILES := $(host_commonSources) +$(call emugl-export,C_INCLUDES,$(EMUGL_PATH)/shared) +$(call emugl-export,LDLIBS,-lstdc++) $(call emugl-end-module) -$(call emugl-begin-host-static-library,lib64emugl_common) -LOCAL_SRC_FILES := $(host_commonSources) -$(call emugl-export,CFLAGS,-m64) -$(call emugl-end-module) +ifdef EMUGL_BUILD_64BITS + $(call emugl-begin-host-static-library,lib64emugl_common) + LOCAL_SRC_FILES := $(host_commonSources) + $(call emugl-export,CFLAGS,-m64 -fPIC) + $(call emugl-export,C_INCLUDES,$(EMUGL_PATH)/shared) + $(call emugl-export,LDLIBS,-lstdc++) + $(call emugl-end-module) +endif ### emugl_common_unittests ############################################## host_commonSources := \ + id_to_object_map_unittest.cpp \ lazy_instance_unittest.cpp \ + pod_vector_unittest.cpp \ mutex_unittest.cpp \ smart_ptr_unittest.cpp \ thread_store_unittest.cpp \ @@ -35,7 +46,9 @@ LOCAL_SRC_FILES := $(host_commonSources) $(call emugl-import,libemugl_common libemugl_gtest) $(call emugl-end-module) -$(call emugl-begin-host-executable,emugl64_common_host_unittests) -LOCAL_SRC_FILES := $(host_commonSources) -$(call emugl-import,lib64emugl_common lib64emugl_gtest) -$(call emugl-end-module) +ifdef EMUGL_BUILD_64BITS + $(call emugl-begin-host-executable,emugl64_common_host_unittests) + LOCAL_SRC_FILES := $(host_commonSources) + $(call emugl-import,lib64emugl_common lib64emugl_gtest) + $(call emugl-end-module) +endif diff --git a/emulator/opengl/shared/emugl/common/id_to_object_map.cpp b/emulator/opengl/shared/emugl/common/id_to_object_map.cpp new file mode 100644 index 0000000..597c9eb --- /dev/null +++ b/emulator/opengl/shared/emugl/common/id_to_object_map.cpp @@ -0,0 +1,236 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// 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. + +#include "emugl/common/id_to_object_map.h" + +#include <stdlib.h> + +namespace emugl { + +namespace { + +typedef IdToObjectMapBase::KeyType KeyType; + +enum { + kMinShift = 3, + kMaxShift = 31, + kMinCapacity = (1 << kMinShift), + kLoadScale = 1024, + kMinLoad = kLoadScale/4, // 25% minimum load. + kMaxLoad = kLoadScale*3/4, // 75% maximum load. + + kInvalidKey = IdToObjectMapBase::kMaxId + 1U, + kTombstone = IdToObjectMapBase::kMaxId + 2U, +}; + +// Return a number that indicates if the current |capacity| is appropriate +// to hold |size| items in our map. +// -1 -> the capacity is too small and needs to be increased. +// 0 -> the capacity is ok. +// +1 -> the capacity is too large and needs to be decreased. +int capacityCompare(size_t shift, size_t size) { + size_t capacity = 1U << shift; + // Essentially, one can rewrite: + // load < minLoad + // as: + // size / capacity < minLoad + // capacity * minLoad > size + if (capacity * kMinLoad > size * kLoadScale) + return +1; + + // Similarly, one can rewrite: + // load > maxLoad + // as: + // size / capacity > maxLoad + // capacity * maxLoad < size + if (capacity * kMaxLoad < size * kLoadScale) + return -1; + + return 0; +} + +size_t probeKeys(const KeyType* keys, size_t shift, KeyType key) { + static const int kPrimes[] = { + 1, /* For 1 << 0 */ + 2, + 3, + 7, + 13, + 31, + 61, + 127, + 251, + 509, + 1021, + 2039, + 4093, + 8191, + 16381, + 32749, + 65521, /* For 1 << 16 */ + 131071, + 262139, + 524287, + 1048573, + 2097143, + 4194301, + 8388593, + 16777213, + 33554393, + 67108859, + 134217689, + 268435399, + 536870909, + 1073741789, + 2147483647 /* For 1 << 31 */ + }; + + size_t slot = key % kPrimes[shift]; + size_t step = 0; + for (;;) { + KeyType k = keys[slot]; + if (k == kInvalidKey || k == kTombstone || k == key) + return slot; + + step += 1; + slot = (slot + step) & (1U << shift); + } +} + +} // namespace + +IdToObjectMapBase::IdToObjectMapBase() : + mCount(0), mShift(kMinShift) { + size_t capacity = 1U << mShift; + mKeys = static_cast<KeyType*>(::calloc(sizeof(mKeys[0]), capacity)); + mValues = static_cast<void**>(::calloc(sizeof(mValues[0]), capacity)); + for (size_t n = 0; n < capacity; ++n) { + mKeys[n] = kInvalidKey; + } +} + +IdToObjectMapBase::~IdToObjectMapBase() { + mShift = 0; + mCount = 0; + ::free(mKeys); + ::free(mValues); +} + +bool IdToObjectMapBase::contains(KeyType key) const { + size_t slot = probeKeys(mKeys, mShift, key); + switch (mKeys[slot]) { + case kInvalidKey: + case kTombstone: + return false; + default: + ; + } + return true; +} + +bool IdToObjectMapBase::find(KeyType key, void** value) const { + size_t slot = probeKeys(mKeys, mShift, key); + if (!isValidKey(mKeys[slot])) { + *value = NULL; + return false; + } + *value = mValues[slot]; + return true; +} + +void* IdToObjectMapBase::set(KeyType key, void* value) { + if (!value) + return remove(key); + + size_t slot = probeKeys(mKeys, mShift, key); + void* result; + if (isValidKey(mKeys[slot])) { + result = mValues[slot]; + mValues[slot] = value; + } else { + mKeys[slot] = key; + mValues[slot] = value; + result = NULL; + mCount++; + resize(mCount); + } + return result; +} + +void* IdToObjectMapBase::remove(KeyType key) { + size_t slot = probeKeys(mKeys, mShift, key); + if (!isValidKey(mKeys[slot])) + return NULL; + + void* result = mValues[slot]; + mValues[slot] = NULL; + mKeys[slot] = kTombstone; + mCount--; + return result; +} + +void IdToObjectMapBase::resize(size_t newSize) { + int ret = capacityCompare(mShift, newSize); + if (!ret) + return; + + size_t oldCapacity = 1U << mShift; + size_t newShift = mShift; + + if (ret < 0) { + // Capacity is too small and must be increased. + do { + if (newShift == kMaxShift) + break; + ++newShift; + } while (capacityCompare(newShift, newSize) < 0); + } else { + // Capacity is too large and must be decreased. + do { + if (newShift == kMinShift) + break; + newShift--; + } while (capacityCompare(newShift, newSize) > 0); + } + if (newShift == mShift) + return; + + // Allocate new arrays. + size_t newCapacity = 1U << newShift; + KeyType* newKeys = static_cast<KeyType*>( + ::calloc(sizeof(newKeys[0]), newCapacity)); + void** newValues = static_cast<void**>( + ::calloc(sizeof(newValues[0]), newCapacity)); + for (size_t n = 0; n < newCapacity; ++n) + newKeys[n] = kInvalidKey; + + // Copy old entries into new arrays. + for (size_t n = 0; n < oldCapacity; ++n) { + KeyType key = mKeys[n]; + if (isValidKey(key)) { + size_t newSlot = probeKeys(newKeys, newShift, key); + newKeys[newSlot] = key; + newValues[newSlot] = mValues[n]; + } + } + + // Swap arrays, and get rid of old ones. + ::free(mKeys); + ::free(mValues); + mKeys = newKeys; + mValues = newValues; + mShift = newShift; +} + +} // namespace emugl diff --git a/emulator/opengl/shared/emugl/common/id_to_object_map.h b/emulator/opengl/shared/emugl/common/id_to_object_map.h new file mode 100644 index 0000000..e3d0a81 --- /dev/null +++ b/emulator/opengl/shared/emugl/common/id_to_object_map.h @@ -0,0 +1,176 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// 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. + +#ifndef EMUGL_COMMON_ID_TO_OBJECT_MAP_H +#define EMUGL_COMMON_ID_TO_OBJECT_MAP_H + +#include <stddef.h> + +namespace emugl { + +// Base implementation class for IdToObjectMap template. +// Used to reduce template-instanciated code generation. +class IdToObjectMapBase { +public: + // The type of keys in this map. + typedef unsigned KeyType; + + // Values higher than kMaxId cannot be used as map keys. + enum { + kMaxId = 0xfffffffdU, + }; + + static inline bool isValidKey(KeyType key) { + return key <= kMaxId; + } + +protected: + IdToObjectMapBase(); + + ~IdToObjectMapBase(); + + void clear(); + + // Return size + inline size_t size() const { return mCount; } + + inline size_t capacity() const { return 1U << mShift; } + + // Return true iff the map contains a given key. + bool contains(KeyType key) const; + + // Find a value associated with a given |key| in the map. + // On success, return true and sets |*value| to the value/pointer, + // which is _still_ owned by the map. + // On failure, return false and sets |*value| to NULL. + bool find(KeyType key, void** value) const; + + // Associate a value with a given |key| in the map. + // Return the old value for the key, if any. Caller is responsible + // for freeing it. + void* set(KeyType key, void* value); + + // Remove the value associated with a given |key|. + // Return the old value, if any. Caller is responsible for + // freeing it. + void* remove(KeyType key); + + size_t mCount; + size_t mShift; + KeyType* mKeys; + void** mValues; + +private: + // Resize the map if needed to ensure it can hold at least |newSize| + // entries. + void resize(size_t newSize); +}; + +// A templated data container that acts as a dictionary mapping unsigned +// integer keys to heap-allocated objects of type T. The dictionary +// owns the objects associated with its keys, and automatically destroys +// them when it is destroyed, or during replacement or removal. +template <class T> +class IdToObjectMap : public IdToObjectMapBase { +public: + // Initialize an empty instance. + IdToObjectMap() : IdToObjectMapBase() {} + + // Destroy this instance. + ~IdToObjectMap() { + clear(); + } + + // Return the number of items in this map. + inline size_t size() const { return IdToObjectMapBase::size(); } + + // Return true iff the map is empty. + inline bool empty() const { return !IdToObjectMapBase::size(); } + + // Remove all items from the map. + void clear(); + + // Returns true iff the dictionary contains a value for |key|. + inline bool contains(KeyType key) const { + return IdToObjectMapBase::contains(key); + } + + // Find the value corresponding to |key| in this map. + // On success, return true, and sets |*value| to point to the + // value (still owned by the instance). On failure, return false. + inline bool find(KeyType key, T** value) const { + return IdToObjectMapBase::find(key, reinterpret_cast<void**>(value)); + } + + // Return the value associated with a given |key|, or NULL if it is + // not in the map. Result is still owned by the map. + inline T* get(KeyType key) const { + T* result = NULL; + this->find(key, &result); + return result; + } + + // Associate |value| with a given |key|. Returns true if a previous + // value was replaced, and false if this is the first time a value + // was associated with the given key. IMPORTANT: This transfers + // ownership of |value| to the map instance. In case of replacement, + // the old value is automatically destroyed. Using NULL as the value + // is equivalent to calling remove(). + bool set(KeyType key, T* value); + + // Remove any value associated with |key|. + // Return true iff a value was associated with the key and destroyed + // by this function, false if there was no value associated with the + // key (or if it was NULL). + bool remove(KeyType key); +}; + +template <class T> +void IdToObjectMap<T>::clear() { + size_t n = capacity(); + while (n > 0) { + --n; + if (!isValidKey(mKeys[n])) + continue; + + delete static_cast<T*>(mValues[n]); + mValues[n] = NULL; + mKeys[n] = kMaxId + 1U; + } + mCount = 0; +} + +template <class T> +bool IdToObjectMap<T>::set(KeyType key, T* value) { + T* oldValue = static_cast<T*>(IdToObjectMapBase::set(key, value)); + if (!oldValue) { + return false; + } + delete oldValue; + return true; +} + +template <class T> +bool IdToObjectMap<T>::remove(KeyType key) { + T* oldValue = static_cast<T*>(IdToObjectMapBase::remove(key)); + if (!oldValue) + return false; + delete oldValue; + return true; +} + +} // namespace emugl + + +#endif // EMUGL_COMMON_ID_TO_OBJECT_MAP_H diff --git a/emulator/opengl/shared/emugl/common/id_to_object_map_unittest.cpp b/emulator/opengl/shared/emugl/common/id_to_object_map_unittest.cpp new file mode 100644 index 0000000..50740be --- /dev/null +++ b/emulator/opengl/shared/emugl/common/id_to_object_map_unittest.cpp @@ -0,0 +1,116 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// 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. + +#include "emugl/common/id_to_object_map.h" + +#include <gtest/gtest.h> + +namespace emugl { + +namespace { + +typedef IdToObjectMapBase::KeyType KeyType; + +class Foo { +public: + Foo() : mVal(0) {} + Foo(int val) : mVal(val) {} + ~Foo() {} + int val() const { return mVal; } + void setVal(int val) { mVal = val; } +private: + int mVal; +}; + +} // namespace + +TEST(IdToObjectMap, Empty) { + IdToObjectMap<Foo> map; + EXPECT_TRUE(map.empty()); + EXPECT_EQ(0U, map.size()); +} + +TEST(IdToObjectMap, SetIntegerRange) { + IdToObjectMap<Foo> map; + KeyType kMax = 10000; + + // Add all items in the map. + for (KeyType n = 0; n < kMax; ++n) { + EXPECT_FALSE(map.set(n, new Foo(n))) << "For key " << n; + } + + // Check final size. + EXPECT_EQ(static_cast<size_t>(kMax), map.size()); + + // Find all items in the map. + for (KeyType n = 0; n < kMax; ++n) { + EXPECT_TRUE(map.contains(n)) << "For key " << n; + Foo* foo = NULL; + EXPECT_TRUE(map.find(n, &foo)) << "For key " << n; + if (foo) { + EXPECT_EQ(static_cast<int>(n), foo->val()) << "For key " << n; + } + } +} + +TEST(IdToObjectMap, RemoveAll) { + IdToObjectMap<Foo> map; + KeyType kMax = 10000; + + // Add all items in the map. + for (KeyType n = 0; n < kMax; ++n) { + EXPECT_FALSE(map.set(n, new Foo(n))) << "For key " << n; + } + + EXPECT_EQ(static_cast<size_t>(kMax), map.size()); + + for (KeyType n = 0; n < kMax; ++n) { + EXPECT_TRUE(map.remove(n)) << "For key " << n; + } + EXPECT_EQ(0U, map.size()); +} + +TEST(IdToObjectMap, RemoveOdd) { + IdToObjectMap<Foo> map; + KeyType kMax = 10000; + + // Add all items in the map. + for (KeyType n = 0; n < kMax; ++n) { + EXPECT_FALSE(map.set(n, new Foo(n))) << "For key " << n; + } + + EXPECT_EQ(static_cast<size_t>(kMax), map.size()); + + for (KeyType n = 0; n < kMax; ++n) { + if (n & 1) { + EXPECT_TRUE(map.remove(n)) << "For key " << n; + } + } + EXPECT_EQ(static_cast<size_t>(kMax / 2), map.size()); + + for (KeyType n = 0; n < kMax; ++n) { + if (n & 1) { + EXPECT_FALSE(map.contains(n)) << "For key " << n; + } else { + EXPECT_TRUE(map.contains(n)) << "For key " << n; + Foo* foo = NULL; + EXPECT_TRUE(map.find(n, &foo)) << "For key " << n; + if (foo) { + EXPECT_EQ(static_cast<int>(n), foo->val()); + } + } + } +} + +} // namespace emugl diff --git a/emulator/opengl/shared/emugl/common/pod_vector.cpp b/emulator/opengl/shared/emugl/common/pod_vector.cpp new file mode 100644 index 0000000..3fe8f15 --- /dev/null +++ b/emulator/opengl/shared/emugl/common/pod_vector.cpp @@ -0,0 +1,151 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// 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. + +#include "emugl/common/pod_vector.h" + +#include <stdlib.h> +#include <string.h> + +#define USE_MALLOC_USABLE_SIZE 0 + +namespace emugl { + +static inline void swapPointers(char** p1, char** p2) { + char* tmp = *p1; + *p1 = *p2; + *p2 = tmp; +} + +PodVectorBase::PodVectorBase(const PodVectorBase& other) { + initFrom(other.begin(), other.byteSize()); +} + +PodVectorBase& PodVectorBase::operator=(const PodVectorBase& other) { + initFrom(other.begin(), other.byteSize()); + return *this; +} + +PodVectorBase::~PodVectorBase() { + if (mBegin) { + // Sanity. + ::memset(mBegin, 0xee, byteSize()); + ::free(mBegin); + mBegin = NULL; + mEnd = NULL; + mLimit = NULL; + } +} + +void PodVectorBase::initFrom(const void* from, size_t fromLen) { + if (!fromLen || !from) { + mBegin = NULL; + mEnd = NULL; + mLimit = NULL; + } else { + mBegin = static_cast<char*>(::malloc(fromLen)); + mEnd = mLimit = mBegin + fromLen; + ::memcpy(mBegin, from, fromLen); + } +} + +void PodVectorBase::assignFrom(const PodVectorBase& other) { + resize(other.byteSize(), 1U); + ::memmove(begin(), other.begin(), byteSize()); +} + +void PodVectorBase::resize(size_t newSize, size_t itemSize) { + const size_t kMaxSize = maxItemCapacity(itemSize); + size_t oldCapacity = itemCapacity(itemSize); + const size_t kMinCapacity = 256 / itemSize; + + if (newSize < oldCapacity) { + // Only shrink if the new size is really small. + if (newSize < oldCapacity / 2 && oldCapacity > kMinCapacity) { + reserve(newSize, itemSize); + } + } else if (newSize > oldCapacity) { + size_t newCapacity = oldCapacity; + while (newCapacity < newSize) { + size_t newCapacity2 = newCapacity + (newCapacity >> 2) + 8; + if (newCapacity2 < newCapacity || newCapacity > kMaxSize) { + newCapacity = kMaxSize; + } else { + newCapacity = newCapacity2; + } + } + reserve(newCapacity, itemSize); + } + mEnd = mBegin + newSize * itemSize; +} + +void PodVectorBase::reserve(size_t newSize, size_t itemSize) { + const size_t kMaxSize = maxItemCapacity(itemSize); + if (newSize == 0) { + ::free(mBegin); + mBegin = NULL; + mEnd = NULL; + mLimit = NULL; + return; + } + + size_t oldByteSize = byteSize(); + size_t newByteCapacity = newSize * itemSize; + char* newBegin = static_cast<char*>(::realloc(mBegin, newByteCapacity)); + mBegin = newBegin; + mEnd = newBegin + oldByteSize; +#if USE_MALLOC_USABLE_SIZE + size_t usableSize = malloc_usable_size(mBegin); + if (usableSize > newByteCapacity) { + newByteCapacity = usableSize - (usableSize % itemSize); + } +#endif + mLimit = newBegin + newByteCapacity; + // Sanity. + if (newByteCapacity > oldByteSize) { + ::memset(mBegin + oldByteSize, 0, newByteCapacity - oldByteSize); + } +} + +void PodVectorBase::removeAt(size_t itemPos, size_t itemSize) { + size_t count = itemCount(itemSize); + if (itemPos < count) { + size_t pos = itemPos * itemSize; + ::memmove(mBegin + pos, + mBegin + pos + itemSize, + byteSize() - pos - itemSize); + resize(count - 1U, itemSize); + } +} + +void* PodVectorBase::insertAt(size_t itemPos, size_t itemSize) { + size_t count = this->itemCount(itemSize); + resize(count + 1, itemSize); + size_t pos = itemPos * itemSize; + if (itemPos < count) { + ::memmove(mBegin + pos + itemSize, + mBegin + pos, + count * itemSize - pos); + // Sanity to avoid copying pointers and other bad stuff. + ::memset(mBegin + pos, 0, itemSize); + } + return mBegin + pos; +} + +void PodVectorBase::swapAll(PodVectorBase* other) { + swapPointers(&mBegin, &other->mBegin); + swapPointers(&mEnd, &other->mEnd); + swapPointers(&mLimit, &other->mLimit); +} + +} // namespace emugl diff --git a/emulator/opengl/shared/emugl/common/pod_vector.h b/emulator/opengl/shared/emugl/common/pod_vector.h new file mode 100644 index 0000000..22a8a1d --- /dev/null +++ b/emulator/opengl/shared/emugl/common/pod_vector.h @@ -0,0 +1,267 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// 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. + +#ifndef EMUGL_COMMON_POD_VECTOR_H +#define EMUGL_COMMON_POD_VECTOR_H + + +#include <stddef.h> + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS 1 +#endif +#include <stdint.h> + +#ifndef SIZE_MAX +#error "You must define __STDC_LIMIT_MACROS before including <stddint.h>" +#endif + +namespace emugl { + +// A PodVector is a templated vector-like type that is used to store +// POD-struct compatible items only. This allows the implementation to +// use ::memmove() to move items, and also malloc_usable_size() to +// determine the best capacity. +// +// std::vector<> is capable of doing this in theory, using horrible +// templating tricks that make error messages very difficult to +// understand. +// +// Note that a PodVector can be used to store items that contain pointers, +// as long as these do not point to items in the same container. +// +// The PodVector provides methods that also follow the std::vector<> +// conventions, i.e. push_back() is an alias for append(). +// + +// NOTE: This is a re-implementation of +// external/qemu/android/base/containers/PodVector.h for emugl. + +// PodVectorBase is a base, non-templated, implementation class that all +// PodVector instances derive from. This is used to reduce template +// specialization. Do not use directly, i..e it's an implementation detail. +class PodVectorBase { +protected: + PodVectorBase() : mBegin(NULL), mEnd(NULL), mLimit(NULL) {} + explicit PodVectorBase(const PodVectorBase& other); + PodVectorBase& operator=(const PodVectorBase& other); + ~PodVectorBase(); + + bool empty() const { return mEnd == mBegin; } + + size_t byteSize() const { return mEnd - mBegin; } + + size_t byteCapacity() const { return mLimit - mBegin; } + + void* begin() { return mBegin; } + const void* begin() const { return mBegin; } + void* end() { return mEnd; } + const void* end() const { return mEnd; } + + void* itemAt(size_t pos, size_t itemSize) { + const size_t kMaxCapacity = SIZE_MAX / itemSize; + return mBegin + pos * itemSize; + } + + const void* itemAt(size_t pos, size_t itemSize) const { + const size_t kMaxCapacity = SIZE_MAX / itemSize; + return mBegin + pos * itemSize; + } + + void assignFrom(const PodVectorBase& other); + + inline size_t itemCount(size_t itemSize) const { + return byteSize() / itemSize; + } + + inline size_t itemCapacity(size_t itemSize) const { + return byteCapacity() / itemSize; + } + + inline size_t maxItemCapacity(size_t itemSize) const { + return SIZE_MAX / itemSize; + } + + void resize(size_t newSize, size_t itemSize); + void reserve(size_t newSize, size_t itemSize); + + void removeAt(size_t index, size_t itemSize); + void* insertAt(size_t index, size_t itemSize); + void swapAll(PodVectorBase* other); + + char* mBegin; + char* mEnd; + char* mLimit; + +private: + void initFrom(const void* from, size_t fromLen); +}; + + +// A PodVector<T> holds a vector (dynamically resizable array) or items +// that must be POD-struct compatible (i.e. they cannot have constructors, +// destructors, or virtual members). This allows the implementation to be +// small, fast and efficient, memory-wise. +// +// If you want to implement a vector of C++ objects, consider using +// std::vector<> instead, but keep in mind that this implies a non-trivial +// cost when appending, inserting, removing items in the collection. +// +template <typename T> +class PodVector : public PodVectorBase { +public: + // Default constructor for an empty PodVector<T> + PodVector() : PodVectorBase() {} + + // Copy constructor. This copies all items from |other| into + // the new instance with ::memmove(). + PodVector(const PodVector& other) : PodVectorBase(other) {} + + // Assignment operator. + PodVector& operator=(const PodVector& other) { + this->assignFrom(other); + return *this; + } + + // Destructor, this simply releases the internal storage block that + // holds all the items, but doesn't touch them otherwise. + ~PodVector() {} + + // Return true iff the PodVector<T> instance is empty, i.e. does not + // have any items. + bool empty() const { return PodVectorBase::empty(); } + + // Return the number of items in the current PodVector<T> instance. + size_t size() const { return PodVectorBase::itemCount(sizeof(T)); } + + // Return the current capacity in the current PodVector<T> instance. + // Do not use directly, except if you know what you're doing. Try to + // use resize() or reserve() instead. + size_t capacity() const { + return PodVectorBase::itemCapacity(sizeof(T)); + } + + // Return the maximum capacity of any PodVector<T> instance. + static inline size_t maxCapacity() { return SIZE_MAX / sizeof(T); } + + // Resize the vector to ensure it can hold |newSize| + // items. This may or may not call reserve() under the hood. + // It's a fatal error to try to resize above maxCapacity(). + void resize(size_t newSize) { + PodVectorBase::resize(newSize, sizeof(T)); + } + + // Resize the vector's storage array to ensure that it can hold at + // least |newSize| items. It's a fatal error to try to resize above + // maxCapacity(). + void reserve(size_t newSize) { + PodVectorBase::reserve(newSize, sizeof(T)); + } + + // Return a pointer to the first item in the vector. This is only + // valid until the next call to any function that changes the size + // or capacity of the vector. Can be NULL if the vector is empty. + T* begin() { + return reinterpret_cast<T*>(PodVectorBase::begin()); + } + + // Return a constant pointer to the first item in the vector. This is + // only valid until the next call to any function that changes the + // size of capacity of the vector. + const T* begin() const { + return reinterpret_cast<T*>(PodVectorBase::begin()); + } + + // Return a pointer past the last item in the vector. I.e. if the + // result is not NULL, then |result - 1| points to the last item. + // Can be NULL if the vector is empty. + T* end() { + return reinterpret_cast<T*>(PodVectorBase::end()); + } + + // Return a constant pointer past the last item in the vector. I.e. if + // the result is not NULL, then |result - 1| points to the last item. + // Can be NULL if the vector is empty. + const T* end() const { + return reinterpret_cast<T*>(PodVectorBase::end()); + } + + // Returns a reference to the item a position |index| in the vector. + // It may be a fatal error to access an out-of-bounds position. + T& operator[](size_t index) { + return *reinterpret_cast<T*>( + PodVectorBase::itemAt(index, sizeof(T))); + } + + const T& operator[](size_t index) const { + return *reinterpret_cast<const T*>( + PodVectorBase::itemAt(index, sizeof(T))); + } + + // Increase the vector's size by 1, then move all items past a given + // position to the right. Return the position of the insertion point + // to let the caller copy the content it desires there. It's preferrable + // to use insert() directly, which will do the item copy for you. + T* emplace(size_t index) { + return reinterpret_cast<T*>( + PodVectorBase::insertAt(index, sizeof(T))); + } + + // Insert an item at a given position. |index| is the insertion position + // which must be between 0 and size() included, or a fatal error may + // occur. |item| is a reference to an item to copy into the array, + // byte-by-byte. + void insert(size_t index, const T& item) { + *(this->emplace(index)) = item; + } + + // Prepend an item at the start of a vector. This moves all vector items + // to the right, and is thus costly. |item| is a reference to an item + // to copy to the start of the vector. + void prepend(const T& item) { + *(this->emplace(0U)) = item; + } + + // Append an item at the end of a vector. Specialized version of insert() + // which always uses size() as the insertion position. + void append(const T& item) { + *(this->emplace(this->size())) = item; + } + + // Remove the item at a given position. |index| is the position of the + // item to delete. It must be between 0 and size(), included, or + // a fatal error may occur. Deleting the item at position size() + // doesn't do anything. + void remove(size_t index) { + PodVectorBase::removeAt(index, sizeof(T)); + } + + void swap(PodVector* other) { + PodVectorBase::swapAll(other); + } + + // Compatibility methods for std::vector<> + void push_back(const T& item) { append(item); } + void pop() { remove(0U); } + + typedef T* iterator; + typedef const T* const_iterator; +}; + +} // namespace emugl + +#endif // EMUGL_COMMON_POD_VECTOR_H diff --git a/emulator/opengl/shared/emugl/common/pod_vector_unittest.cpp b/emulator/opengl/shared/emugl/common/pod_vector_unittest.cpp new file mode 100644 index 0000000..9ce509a --- /dev/null +++ b/emulator/opengl/shared/emugl/common/pod_vector_unittest.cpp @@ -0,0 +1,127 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// 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. + +#include "emugl/common/pod_vector.h" + +#include <gtest/gtest.h> + +namespace emugl { + +static int hashIndex(size_t n) { + return static_cast<int>(((n >> 14) * 13773) + (n * 51)); +} + +TEST(PodVector, Empty) { + PodVector<int> v; + EXPECT_TRUE(v.empty()); + EXPECT_EQ(0U, v.size()); +} + +TEST(PodVector, AppendOneItem) { + PodVector<int> v; + v.append(10234); + EXPECT_FALSE(v.empty()); + EXPECT_EQ(1U, v.size()); + EXPECT_EQ(10234, v[0]); +} + +TEST(PodVector, AppendLotsOfItems) { + PodVector<int> v; + const size_t kMaxCount = 10000; + for (size_t n = 0; n < kMaxCount; ++n) { + v.append(hashIndex(n)); + } + EXPECT_EQ(kMaxCount, v.size()); + for (size_t n = 0; n < kMaxCount; ++n) { + EXPECT_EQ(hashIndex(n), v[n]) << "At index " << n; + } +} + +TEST(PodVector, RemoveFrontItems) { + PodVector<int> v; + const size_t kMaxCount = 100; + for (size_t n = 0; n < kMaxCount; ++n) { + v.append(hashIndex(n)); + } + EXPECT_EQ(kMaxCount, v.size()); + for (size_t n = 0; n < kMaxCount; ++n) { + EXPECT_EQ(hashIndex(n), v[0]) << "At index " << n; + v.remove(0U); + EXPECT_EQ(kMaxCount - n - 1U, v.size()) << "At index " << n; + } +} + +TEST(PodVector, PrependItems) { + PodVector<int> v; + const size_t kMaxCount = 100; + for (size_t n = 0; n < kMaxCount; ++n) { + v.prepend(hashIndex(n)); + } + EXPECT_EQ(kMaxCount, v.size()); + for (size_t n = 0; n < kMaxCount; ++n) { + EXPECT_EQ(hashIndex(kMaxCount - n - 1), v[n]) << "At index " << n; + } +} + +TEST(PodVector, ResizeExpands) { + PodVector<int> v; + const size_t kMaxCount = 100; + const size_t kMaxCount2 = 10000; + for (size_t n = 0; n < kMaxCount; ++n) { + v.append(hashIndex(n)); + } + EXPECT_EQ(kMaxCount, v.size()); + v.resize(kMaxCount2); + EXPECT_EQ(kMaxCount2, v.size()); + for (size_t n = 0; n < kMaxCount; ++n) { + EXPECT_EQ(hashIndex(n), v[n]) << "At index " << n; + } +} + +TEST(PodVector, ResizeTruncates) { + PodVector<int> v; + const size_t kMaxCount = 10000; + const size_t kMaxCount2 = 10; + for (size_t n = 0; n < kMaxCount; ++n) { + v.append(hashIndex(n)); + } + EXPECT_EQ(kMaxCount, v.size()); + v.resize(kMaxCount2); + EXPECT_EQ(kMaxCount2, v.size()); + for (size_t n = 0; n < kMaxCount2; ++n) { + EXPECT_EQ(hashIndex(n), v[n]) << "At index " << n; + } +} + + +TEST(PodVector, AssignmentOperator) { + PodVector<int> v1; + const size_t kMaxCount = 10000; + for (size_t n = 0; n < kMaxCount; ++n) { + v1.append(hashIndex(n)); + } + EXPECT_EQ(kMaxCount, v1.size()); + + PodVector<int> v2; + v2 = v1; + + v1.reserve(0); + + EXPECT_EQ(kMaxCount, v2.size()); + for (size_t n = 0; n < kMaxCount; ++n) { + EXPECT_EQ(hashIndex(n), v2[n]) << "At index " << n; + } +} + +} // namespace emugl diff --git a/emulator/opengl/shared/emugl/common/scoped_pointer_vector.h b/emulator/opengl/shared/emugl/common/scoped_pointer_vector.h new file mode 100644 index 0000000..3d263e9 --- /dev/null +++ b/emulator/opengl/shared/emugl/common/scoped_pointer_vector.h @@ -0,0 +1,27 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// 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. + +#ifndef EMUGL_COMMON_SCOPED_POINTER_VECTOR_H +#define EMUGL_COMMON_SCOPED_POINTER_VECTOR_H + +namespace emugl { + +template <class T> +class ScopedPointerVector { + ScopedPointerVector +}; + +} // namespace emugl + +#endif // EMUGL_COMMON_SCOPED_POINTER_VECTOR_H diff --git a/emulator/opengl/shared/emugl/common/sockets.cpp b/emulator/opengl/shared/emugl/common/sockets.cpp new file mode 100644 index 0000000..feb6d38 --- /dev/null +++ b/emulator/opengl/shared/emugl/common/sockets.cpp @@ -0,0 +1,221 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// 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. + +#include "emugl/common/sockets.h" + +#include <errno.h> + +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#else +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/un.h> +#include <sys/stat.h> +#endif + +#include <stddef.h> +#include <string.h> +#include <unistd.h> + +namespace emugl { + +namespace { + +static void socketSetReuseAddress(int s) { +#ifdef _WIN32 + // The default behaviour on WIndows is equivalent to SO_REUSEADDR + // so we don't need to set this option. Moreover, one should never + // set this option with WinSock because it's badly implemented and + // generates a huge security issue. See: + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621(v=vs.85).aspx +#else + int val = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); +#endif +} + +// Helper union to store a socket address. +struct SockAddr { + socklen_t len; + union { + sockaddr generic; + sockaddr_in inet; +#ifndef _WIN32 + sockaddr_un local; +#endif + }; + + int getFamily() const { return generic.sa_family; } + + void initEmpty() { + ::memset(this, 0, sizeof(*this)); + this->len = static_cast<socklen_t>(sizeof(*this)); + } + + int initFromInet(uint32_t ip_address, int port) { + if (port < 0 || port >= 65536) + return -EINVAL; + + ::memset(this, 0, sizeof(*this)); + this->inet.sin_family = AF_INET; + this->inet.sin_port = htons(port); + this->inet.sin_addr.s_addr = htonl(ip_address); + this->len = sizeof(this->inet); + return 0; + } + + int initFromLocalhost(int port) { + return initFromInet(0x7f000001, port); + } + +#ifndef _WIN32 + // Initialize the SockAddr from a Unix path. Returns 0 on success, + // or -errno code on failure. + int initFromUnixPath(const char* path) { + if (!path || !path[0]) + return -EINVAL; + + size_t pathLen = ::strlen(path); + if (pathLen >= sizeof(local.sun_path)) + return -E2BIG; + + ::memset(this, 0, sizeof(*this)); + this->local.sun_family = AF_LOCAL; + ::memcpy(this->local.sun_path, path, pathLen + 1U); + this->len = pathLen + offsetof(sockaddr_un, sun_path); + return 0; + } +#endif +}; + +int socketBindInternal(const SockAddr* addr, int socketType) { + int s = ::socket(addr->getFamily(), socketType, 0); + if (s < 0) + return -errno; + + // Bind to the socket. + if (::bind(s, &addr->generic, addr->len) < 0 || + ::listen(s, 5) < 0) { + int ret = -errno; + ::close(s); + return ret; + } + + socketSetReuseAddress(s); + return s; +} + +int socketConnectInternal(const SockAddr* addr, int socketType) { + int s = ::socket(addr->getFamily(), socketType, 0); + if (s < 0) + return -errno; + + int ret; + do { + ret = ::connect(s, &addr->generic, addr->len); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + ret = -errno; + ::close(s); + return ret; + } + + return s; +} + +} // namespace + +void socketTcpDisableNagle(int s) { + // disable Nagle algorithm to improve bandwidth of small + // packets which are quite common in our implementation. +#ifdef _WIN32 + DWORD flag; +#else + int flag; +#endif + flag = 1; + setsockopt(s, IPPROTO_TCP, TCP_NODELAY, + (const char*)&flag, sizeof(flag)); +} + +int socketGetPort(int s) { + SockAddr addr; + addr.initEmpty(); + if (getsockname(s, &addr.generic, &addr.len) < 0) { + return -errno; + } + switch (addr.generic.sa_family) { + case AF_INET: + return ntohs(addr.inet.sin_port); + default: + ; + } + return -EINVAL; +} + +#ifndef _WIN32 +int socketLocalServer(const char* path, int socketType) { + SockAddr addr; + int ret = addr.initFromUnixPath(path); + if (ret < 0) { + return ret; + } + return socketBindInternal(&addr, socketType); +} + +int socketLocalClient(const char* path, int socketType) { + SockAddr addr; + int ret = addr.initFromUnixPath(path); + if (ret < 0) { + return ret; + } + return socketConnectInternal(&addr, socketType); +} +#endif // !_WIN32 + +int socketTcpLoopbackServer(int port, int socketType) { + SockAddr addr; + int ret = addr.initFromLocalhost(port); + if (ret < 0) { + return ret; + } + return socketBindInternal(&addr, socketType); +} + +int socketTcpLoopbackClient(int port, int socketType) { + SockAddr addr; + int ret = addr.initFromLocalhost(port); + if (ret < 0) { + return ret; + } + return socketConnectInternal(&addr, socketType); +} + +int socketTcpClient(const char* hostname, int port, int socketType) { + // TODO(digit): Implement this. + return -ENOSYS; +} + +int socketAccept(int serverSocket) { + int ret; + do { + ret = ::accept(serverSocket, NULL, NULL); + } while (ret < 0 && errno == EINTR); + return ret; +} + +} // namespace emugl diff --git a/emulator/opengl/shared/emugl/common/sockets.h b/emulator/opengl/shared/emugl/common/sockets.h new file mode 100644 index 0000000..11e7ac7 --- /dev/null +++ b/emulator/opengl/shared/emugl/common/sockets.h @@ -0,0 +1,57 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// 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. + +#ifndef EMUGL_COMMON_SOCKETS_H +#define EMUGL_COMMON_SOCKETS_H + +// A set of helper functions used to deal with sockets in a portable way. + +namespace emugl { + +// Disable Nagle's algorithm for socket descriptor |s|. This assumes +// that |s| is a TCP socket descriptor. +void socketTcpDisableNagle(int s); + +// Return the port associated with a given IP or IPv6 socket, +// or -errno code on failure. +int socketGetPort(int s); + +// Bind to a local/Unix path, and return its socket descriptor on success, +// or -errno code on failure. +int socketLocalServer(const char* path, int socketType); + +// Connect to a Unix local path, and return a new socket descriptor +// on success, or -errno code on failure. +int socketLocalClient(const char* path, int socketType); + +// Bind to a localhost TCP socket, and return its socket descriptor on +// success, or -errno code on failure. +int socketTcpLoopbackServer(int port, int socketType); + +// Connect to a localhost TCP port, and return a new socket descriptor on +// success, or -errno code on failure. +int socketTcpLoopbackClient(int port, int socketType); + +// Connect to a TCP host, and return a new socket descriptor on +// success, or -errno code on failure. +int socketTcpClient(const char* hostname, int port, int socketType); + +// Accept a new connection. |serverSocket| must be a bound server socket +// descriptor. Returns new socket descriptor on success, or -errno code +// on failure. +int socketAccept(int serverSocket); + +} // namespace emugl + +#endif // EMUGL_COMMON_SOCKETS_H |
