Commit ab93bbe2aebebce9901e740b1000058f74c15e26

Authored by bellard
1 parent 0f0b7264

soft mmu support


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@349 c046a42c-6fe2-441c-8c8c-71466251a162
bswap.h 0 โ†’ 100644
  1 +#ifndef BSWAP_H
  2 +#define BSWAP_H
  3 +
  4 +#include "config-host.h"
  5 +
  6 +#include <inttypes.h>
  7 +
  8 +#ifdef HAVE_BYTESWAP_H
  9 +#include <byteswap.h>
  10 +#else
  11 +
  12 +#define bswap_16(x) \
  13 +({ \
  14 + uint16_t __x = (x); \
  15 + ((uint16_t)( \
  16 + (((uint16_t)(__x) & (uint16_t)0x00ffU) << 8) | \
  17 + (((uint16_t)(__x) & (uint16_t)0xff00U) >> 8) )); \
  18 +})
  19 +
  20 +#define bswap_32(x) \
  21 +({ \
  22 + uint32_t __x = (x); \
  23 + ((uint32_t)( \
  24 + (((uint32_t)(__x) & (uint32_t)0x000000ffUL) << 24) | \
  25 + (((uint32_t)(__x) & (uint32_t)0x0000ff00UL) << 8) | \
  26 + (((uint32_t)(__x) & (uint32_t)0x00ff0000UL) >> 8) | \
  27 + (((uint32_t)(__x) & (uint32_t)0xff000000UL) >> 24) )); \
  28 +})
  29 +
  30 +#define bswap_64(x) \
  31 +({ \
  32 + uint64_t __x = (x); \
  33 + ((uint64_t)( \
  34 + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x00000000000000ffULL) << 56) | \
  35 + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x000000000000ff00ULL) << 40) | \
  36 + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x0000000000ff0000ULL) << 24) | \
  37 + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x00000000ff000000ULL) << 8) | \
  38 + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x000000ff00000000ULL) >> 8) | \
  39 + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x0000ff0000000000ULL) >> 24) | \
  40 + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x00ff000000000000ULL) >> 40) | \
  41 + (uint64_t)(((uint64_t)(__x) & (uint64_t)0xff00000000000000ULL) >> 56) )); \
  42 +})
  43 +
  44 +#endif /* !HAVE_BYTESWAP_H */
  45 +
  46 +#if defined(__alpha__) || defined (__ia64__)
  47 +#define HOST_LONG_BITS 64
  48 +#else
  49 +#define HOST_LONG_BITS 32
  50 +#endif
  51 +
  52 +#define HOST_LONG_SIZE (HOST_LONG_BITS / 8)
  53 +
  54 +static inline uint16_t bswap16(uint16_t x)
  55 +{
  56 + return bswap_16(x);
  57 +}
  58 +
  59 +static inline uint32_t bswap32(uint32_t x)
  60 +{
  61 + return bswap_32(x);
  62 +}
  63 +
  64 +static inline uint64_t bswap64(uint64_t x)
  65 +{
  66 + return bswap_64(x);
  67 +}
  68 +
  69 +static inline void bswap16s(uint16_t *s)
  70 +{
  71 + *s = bswap16(*s);
  72 +}
  73 +
  74 +static inline void bswap32s(uint32_t *s)
  75 +{
  76 + *s = bswap32(*s);
  77 +}
  78 +
  79 +static inline void bswap64s(uint64_t *s)
  80 +{
  81 + *s = bswap64(*s);
  82 +}
  83 +
  84 +#endif /* BSWAP_H */
... ...
cpu-defs.h 0 โ†’ 100644
  1 +/*
  2 + * common defines for all CPUs
  3 + *
  4 + * Copyright (c) 2003 Fabrice Bellard
  5 + *
  6 + * This library is free software; you can redistribute it and/or
  7 + * modify it under the terms of the GNU Lesser General Public
  8 + * License as published by the Free Software Foundation; either
  9 + * version 2 of the License, or (at your option) any later version.
  10 + *
  11 + * This library is distributed in the hope that it will be useful,
  12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14 + * Lesser General Public License for more details.
  15 + *
  16 + * You should have received a copy of the GNU Lesser General Public
  17 + * License along with this library; if not, write to the Free Software
  18 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  19 + */
  20 +#ifndef CPU_DEFS_H
  21 +#define CPU_DEFS_H
  22 +
  23 +#include "config.h"
  24 +#include <setjmp.h>
  25 +
  26 +#define EXCP_INTERRUPT 256 /* async interruption */
  27 +#define EXCP_HLT 257 /* hlt instruction reached */
  28 +#define EXCP_DEBUG 258 /* cpu stopped after a breakpoint or singlestep */
  29 +
  30 +#define MAX_BREAKPOINTS 32
  31 +
  32 +#define CPU_TLB_SIZE 256
  33 +
  34 +typedef struct CPUTLBEntry {
  35 + uint32_t address;
  36 + uint32_t addend;
  37 +} CPUTLBEntry;
  38 +
  39 +#endif
... ...
helper2-i386.c 0 โ†’ 100644
  1 +/*
  2 + * i386 helpers (without register variable usage)
  3 + *
  4 + * Copyright (c) 2003 Fabrice Bellard
  5 + *
  6 + * This library is free software; you can redistribute it and/or
  7 + * modify it under the terms of the GNU Lesser General Public
  8 + * License as published by the Free Software Foundation; either
  9 + * version 2 of the License, or (at your option) any later version.
  10 + *
  11 + * This library is distributed in the hope that it will be useful,
  12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14 + * Lesser General Public License for more details.
  15 + *
  16 + * You should have received a copy of the GNU Lesser General Public
  17 + * License along with this library; if not, write to the Free Software
  18 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  19 + */
  20 +#include <stdarg.h>
  21 +#include <stdlib.h>
  22 +#include <stdio.h>
  23 +#include <string.h>
  24 +#include <inttypes.h>
  25 +#include <signal.h>
  26 +#include <assert.h>
  27 +#include <sys/mman.h>
  28 +
  29 +#include "cpu-i386.h"
  30 +#include "exec.h"
  31 +
  32 +//#define DEBUG_MMU
  33 +
  34 +CPUX86State *cpu_x86_init(void)
  35 +{
  36 + CPUX86State *env;
  37 + int i;
  38 + static int inited;
  39 +
  40 + cpu_exec_init();
  41 +
  42 + env = malloc(sizeof(CPUX86State));
  43 + if (!env)
  44 + return NULL;
  45 + memset(env, 0, sizeof(CPUX86State));
  46 + /* basic FPU init */
  47 + for(i = 0;i < 8; i++)
  48 + env->fptags[i] = 1;
  49 + env->fpuc = 0x37f;
  50 + /* flags setup : we activate the IRQs by default as in user mode */
  51 + env->eflags = 0x2 | IF_MASK;
  52 +
  53 + tlb_flush(env);
  54 +#ifdef CONFIG_SOFTMMU
  55 + env->soft_mmu = 1;
  56 +#endif
  57 + /* init various static tables */
  58 + if (!inited) {
  59 + inited = 1;
  60 + optimize_flags_init();
  61 + }
  62 + return env;
  63 +}
  64 +
  65 +void cpu_x86_close(CPUX86State *env)
  66 +{
  67 + free(env);
  68 +}
  69 +
  70 +/***********************************************************/
  71 +/* x86 debug */
  72 +
  73 +static const char *cc_op_str[] = {
  74 + "DYNAMIC",
  75 + "EFLAGS",
  76 + "MUL",
  77 + "ADDB",
  78 + "ADDW",
  79 + "ADDL",
  80 + "ADCB",
  81 + "ADCW",
  82 + "ADCL",
  83 + "SUBB",
  84 + "SUBW",
  85 + "SUBL",
  86 + "SBBB",
  87 + "SBBW",
  88 + "SBBL",
  89 + "LOGICB",
  90 + "LOGICW",
  91 + "LOGICL",
  92 + "INCB",
  93 + "INCW",
  94 + "INCL",
  95 + "DECB",
  96 + "DECW",
  97 + "DECL",
  98 + "SHLB",
  99 + "SHLW",
  100 + "SHLL",
  101 + "SARB",
  102 + "SARW",
  103 + "SARL",
  104 +};
  105 +
  106 +void cpu_x86_dump_state(CPUX86State *env, FILE *f, int flags)
  107 +{
  108 + int eflags;
  109 + char cc_op_name[32];
  110 +
  111 + eflags = env->eflags;
  112 + fprintf(f, "EAX=%08x EBX=%08x ECX=%08x EDX=%08x\n"
  113 + "ESI=%08x EDI=%08x EBP=%08x ESP=%08x\n"
  114 + "EIP=%08x EFL=%08x [%c%c%c%c%c%c%c]\n",
  115 + env->regs[R_EAX], env->regs[R_EBX], env->regs[R_ECX], env->regs[R_EDX],
  116 + env->regs[R_ESI], env->regs[R_EDI], env->regs[R_EBP], env->regs[R_ESP],
  117 + env->eip, eflags,
  118 + eflags & DF_MASK ? 'D' : '-',
  119 + eflags & CC_O ? 'O' : '-',
  120 + eflags & CC_S ? 'S' : '-',
  121 + eflags & CC_Z ? 'Z' : '-',
  122 + eflags & CC_A ? 'A' : '-',
  123 + eflags & CC_P ? 'P' : '-',
  124 + eflags & CC_C ? 'C' : '-');
  125 + fprintf(f, "CS=%04x SS=%04x DS=%04x ES=%04x FS=%04x GS=%04x\n",
  126 + env->segs[R_CS].selector,
  127 + env->segs[R_SS].selector,
  128 + env->segs[R_DS].selector,
  129 + env->segs[R_ES].selector,
  130 + env->segs[R_FS].selector,
  131 + env->segs[R_GS].selector);
  132 + if (flags & X86_DUMP_CCOP) {
  133 + if ((unsigned)env->cc_op < CC_OP_NB)
  134 + strcpy(cc_op_name, cc_op_str[env->cc_op]);
  135 + else
  136 + snprintf(cc_op_name, sizeof(cc_op_name), "[%d]", env->cc_op);
  137 + fprintf(f, "CCS=%08x CCD=%08x CCO=%-8s\n",
  138 + env->cc_src, env->cc_dst, cc_op_name);
  139 + }
  140 + if (flags & X86_DUMP_FPU) {
  141 + fprintf(f, "ST0=%f ST1=%f ST2=%f ST3=%f\n",
  142 + (double)env->fpregs[0],
  143 + (double)env->fpregs[1],
  144 + (double)env->fpregs[2],
  145 + (double)env->fpregs[3]);
  146 + fprintf(f, "ST4=%f ST5=%f ST6=%f ST7=%f\n",
  147 + (double)env->fpregs[4],
  148 + (double)env->fpregs[5],
  149 + (double)env->fpregs[7],
  150 + (double)env->fpregs[8]);
  151 + }
  152 +}
  153 +
  154 +/***********************************************************/
  155 +/* x86 mmu */
  156 +/* XXX: add PGE support */
  157 +
  158 +/* called when cr3 or PG bit are modified */
  159 +static int last_pg_state = -1;
  160 +static int last_pe_state = 0;
  161 +int phys_ram_size;
  162 +int phys_ram_fd;
  163 +uint8_t *phys_ram_base;
  164 +
  165 +void cpu_x86_update_cr0(CPUX86State *env)
  166 +{
  167 + int pg_state, pe_state;
  168 +
  169 +#ifdef DEBUG_MMU
  170 + printf("CR0 update: CR0=0x%08x\n", env->cr[0]);
  171 +#endif
  172 + pg_state = env->cr[0] & CR0_PG_MASK;
  173 + if (pg_state != last_pg_state) {
  174 + page_unmap();
  175 + tlb_flush(env);
  176 + last_pg_state = pg_state;
  177 + }
  178 + pe_state = env->cr[0] & CR0_PE_MASK;
  179 + if (last_pe_state != pe_state) {
  180 + tb_flush();
  181 + last_pe_state = pe_state;
  182 + }
  183 +}
  184 +
  185 +void cpu_x86_update_cr3(CPUX86State *env)
  186 +{
  187 + if (env->cr[0] & CR0_PG_MASK) {
  188 +#if defined(DEBUG_MMU)
  189 + printf("CR3 update: CR3=%08x\n", env->cr[3]);
  190 +#endif
  191 + page_unmap();
  192 + tlb_flush(env);
  193 + }
  194 +}
  195 +
  196 +void cpu_x86_init_mmu(CPUX86State *env)
  197 +{
  198 + last_pg_state = -1;
  199 + cpu_x86_update_cr0(env);
  200 +}
  201 +
  202 +/* XXX: also flush 4MB pages */
  203 +void cpu_x86_flush_tlb(CPUX86State *env, uint32_t addr)
  204 +{
  205 + int flags;
  206 + unsigned long virt_addr;
  207 +
  208 + tlb_flush_page(env, addr);
  209 +
  210 + flags = page_get_flags(addr);
  211 + if (flags & PAGE_VALID) {
  212 + virt_addr = addr & ~0xfff;
  213 + munmap((void *)virt_addr, 4096);
  214 + page_set_flags(virt_addr, virt_addr + 4096, 0);
  215 + }
  216 +}
  217 +
  218 +/* return value:
  219 + -1 = cannot handle fault
  220 + 0 = nothing more to do
  221 + 1 = generate PF fault
  222 + 2 = soft MMU activation required for this block
  223 +*/
  224 +int cpu_x86_handle_mmu_fault(CPUX86State *env, uint32_t addr, int is_write)
  225 +{
  226 + uint8_t *pde_ptr, *pte_ptr;
  227 + uint32_t pde, pte, virt_addr;
  228 + int cpl, error_code, is_dirty, is_user, prot, page_size, ret;
  229 + unsigned long pd;
  230 +
  231 + cpl = env->cpl;
  232 + is_user = (cpl == 3);
  233 +
  234 +#ifdef DEBUG_MMU
  235 + printf("MMU fault: addr=0x%08x w=%d u=%d eip=%08x\n",
  236 + addr, is_write, is_user, env->eip);
  237 +#endif
  238 +
  239 + if (env->user_mode_only) {
  240 + /* user mode only emulation */
  241 + error_code = 0;
  242 + goto do_fault;
  243 + }
  244 +
  245 + if (!(env->cr[0] & CR0_PG_MASK)) {
  246 + pte = addr;
  247 + virt_addr = addr & ~0xfff;
  248 + prot = PROT_READ | PROT_WRITE;
  249 + page_size = 4096;
  250 + goto do_mapping;
  251 + }
  252 +
  253 + /* page directory entry */
  254 + pde_ptr = phys_ram_base + ((env->cr[3] & ~0xfff) + ((addr >> 20) & ~3));
  255 + pde = ldl(pde_ptr);
  256 + if (!(pde & PG_PRESENT_MASK)) {
  257 + error_code = 0;
  258 + goto do_fault;
  259 + }
  260 + if (is_user) {
  261 + if (!(pde & PG_USER_MASK))
  262 + goto do_fault_protect;
  263 + if (is_write && !(pde & PG_RW_MASK))
  264 + goto do_fault_protect;
  265 + } else {
  266 + if ((env->cr[0] & CR0_WP_MASK) && (pde & PG_USER_MASK) &&
  267 + is_write && !(pde & PG_RW_MASK))
  268 + goto do_fault_protect;
  269 + }
  270 + /* if PSE bit is set, then we use a 4MB page */
  271 + if ((pde & PG_PSE_MASK) && (env->cr[4] & CR4_PSE_MASK)) {
  272 + is_dirty = is_write && !(pde & PG_DIRTY_MASK);
  273 + if (!(pde & PG_ACCESSED_MASK)) {
  274 + pde |= PG_ACCESSED_MASK;
  275 + if (is_dirty)
  276 + pde |= PG_DIRTY_MASK;
  277 + stl(pde_ptr, pde);
  278 + }
  279 +
  280 + pte = pde & ~0x003ff000; /* align to 4MB */
  281 + page_size = 4096 * 1024;
  282 + virt_addr = addr & ~0x003fffff;
  283 + } else {
  284 + if (!(pde & PG_ACCESSED_MASK)) {
  285 + pde |= PG_ACCESSED_MASK;
  286 + stl(pde_ptr, pde);
  287 + }
  288 +
  289 + /* page directory entry */
  290 + pte_ptr = phys_ram_base + ((pde & ~0xfff) + ((addr >> 10) & 0xffc));
  291 + pte = ldl(pte_ptr);
  292 + if (!(pte & PG_PRESENT_MASK)) {
  293 + error_code = 0;
  294 + goto do_fault;
  295 + }
  296 + if (is_user) {
  297 + if (!(pte & PG_USER_MASK))
  298 + goto do_fault_protect;
  299 + if (is_write && !(pte & PG_RW_MASK))
  300 + goto do_fault_protect;
  301 + } else {
  302 + if ((env->cr[0] & CR0_WP_MASK) && (pte & PG_USER_MASK) &&
  303 + is_write && !(pte & PG_RW_MASK))
  304 + goto do_fault_protect;
  305 + }
  306 + is_dirty = is_write && !(pte & PG_DIRTY_MASK);
  307 + if (!(pte & PG_ACCESSED_MASK) || is_dirty) {
  308 + pte |= PG_ACCESSED_MASK;
  309 + if (is_dirty)
  310 + pte |= PG_DIRTY_MASK;
  311 + stl(pte_ptr, pte);
  312 + }
  313 + page_size = 4096;
  314 + virt_addr = addr & ~0xfff;
  315 + }
  316 + /* the page can be put in the TLB */
  317 + prot = PROT_READ;
  318 + if (is_user) {
  319 + if (pte & PG_RW_MASK)
  320 + prot |= PROT_WRITE;
  321 + } else {
  322 + if (!(env->cr[0] & CR0_WP_MASK) || !(pte & PG_USER_MASK) ||
  323 + (pte & PG_RW_MASK))
  324 + prot |= PROT_WRITE;
  325 + }
  326 +
  327 + do_mapping:
  328 + if (env->soft_mmu) {
  329 + unsigned long paddr, vaddr, address, addend, page_offset;
  330 + int index;
  331 +
  332 + /* software MMU case. Even if 4MB pages, we map only one 4KB
  333 + page in the cache to avoid filling it too fast */
  334 + page_offset = (addr & ~0xfff) & (page_size - 1);
  335 + paddr = (pte & ~0xfff) + page_offset;
  336 + vaddr = virt_addr + page_offset;
  337 + index = (addr >> 12) & (CPU_TLB_SIZE - 1);
  338 + pd = physpage_find(paddr);
  339 + if (pd & 0xfff) {
  340 + /* IO memory case */
  341 + address = vaddr | pd;
  342 + addend = paddr;
  343 + } else {
  344 + /* standard memory */
  345 + address = vaddr;
  346 + addend = (unsigned long)phys_ram_base + pd;
  347 + }
  348 + addend -= vaddr;
  349 + env->tlb_read[is_user][index].address = address;
  350 + env->tlb_read[is_user][index].addend = addend;
  351 + if (prot & PROT_WRITE) {
  352 + env->tlb_write[is_user][index].address = address;
  353 + env->tlb_write[is_user][index].addend = addend;
  354 + }
  355 + }
  356 + ret = 0;
  357 + /* XXX: incorrect for 4MB pages */
  358 + pd = physpage_find(pte & ~0xfff);
  359 + if ((pd & 0xfff) != 0) {
  360 + /* IO access: no mapping is done as it will be handled by the
  361 + soft MMU */
  362 + if (!env->soft_mmu)
  363 + ret = 2;
  364 + } else {
  365 + void *map_addr;
  366 + map_addr = mmap((void *)virt_addr, page_size, prot,
  367 + MAP_SHARED | MAP_FIXED, phys_ram_fd, pd);
  368 + if (map_addr == MAP_FAILED) {
  369 + fprintf(stderr,
  370 + "mmap failed when mapped physical address 0x%08x to virtual address 0x%08x\n",
  371 + pte & ~0xfff, virt_addr);
  372 + exit(1);
  373 + }
  374 +#ifdef DEBUG_MMU
  375 + printf("mmaping 0x%08x to virt 0x%08x pse=%d\n",
  376 + pte & ~0xfff, virt_addr, (page_size != 4096));
  377 +#endif
  378 + page_set_flags(virt_addr, virt_addr + page_size,
  379 + PAGE_VALID | PAGE_EXEC | prot);
  380 + }
  381 + return ret;
  382 + do_fault_protect:
  383 + error_code = PG_ERROR_P_MASK;
  384 + do_fault:
  385 + env->cr[2] = addr;
  386 + env->error_code = (is_write << PG_ERROR_W_BIT) | error_code;
  387 + if (is_user)
  388 + env->error_code |= PG_ERROR_U_MASK;
  389 + return 1;
  390 +}
... ...
ops_mem.h 0 โ†’ 100644
  1 +void OPPROTO glue(glue(op_ldub, MEMSUFFIX), _T0_A0)(void)
  2 +{
  3 + T0 = glue(ldub, MEMSUFFIX)((uint8_t *)A0);
  4 +}
  5 +
  6 +void OPPROTO glue(glue(op_ldsb, MEMSUFFIX), _T0_A0)(void)
  7 +{
  8 + T0 = glue(ldsb, MEMSUFFIX)((int8_t *)A0);
  9 +}
  10 +
  11 +void OPPROTO glue(glue(op_lduw, MEMSUFFIX), _T0_A0)(void)
  12 +{
  13 + T0 = glue(lduw, MEMSUFFIX)((uint8_t *)A0);
  14 +}
  15 +
  16 +void OPPROTO glue(glue(op_ldsw, MEMSUFFIX), _T0_A0)(void)
  17 +{
  18 + T0 = glue(ldsw, MEMSUFFIX)((int8_t *)A0);
  19 +}
  20 +
  21 +void OPPROTO glue(glue(op_ldl, MEMSUFFIX), _T0_A0)(void)
  22 +{
  23 + T0 = glue(ldl, MEMSUFFIX)((uint8_t *)A0);
  24 +}
  25 +
  26 +void OPPROTO glue(glue(op_ldub, MEMSUFFIX), _T1_A0)(void)
  27 +{
  28 + T1 = glue(ldub, MEMSUFFIX)((uint8_t *)A0);
  29 +}
  30 +
  31 +void OPPROTO glue(glue(op_ldsb, MEMSUFFIX), _T1_A0)(void)
  32 +{
  33 + T1 = glue(ldsb, MEMSUFFIX)((int8_t *)A0);
  34 +}
  35 +
  36 +void OPPROTO glue(glue(op_lduw, MEMSUFFIX), _T1_A0)(void)
  37 +{
  38 + T1 = glue(lduw, MEMSUFFIX)((uint8_t *)A0);
  39 +}
  40 +
  41 +void OPPROTO glue(glue(op_ldsw, MEMSUFFIX), _T1_A0)(void)
  42 +{
  43 + T1 = glue(ldsw, MEMSUFFIX)((int8_t *)A0);
  44 +}
  45 +
  46 +void OPPROTO glue(glue(op_ldl, MEMSUFFIX), _T1_A0)(void)
  47 +{
  48 + T1 = glue(ldl, MEMSUFFIX)((uint8_t *)A0);
  49 +}
  50 +
  51 +void OPPROTO glue(glue(op_stb, MEMSUFFIX), _T0_A0)(void)
  52 +{
  53 + glue(stb, MEMSUFFIX)((uint8_t *)A0, T0);
  54 +}
  55 +
  56 +void OPPROTO glue(glue(op_stw, MEMSUFFIX), _T0_A0)(void)
  57 +{
  58 + glue(stw, MEMSUFFIX)((uint8_t *)A0, T0);
  59 +}
  60 +
  61 +void OPPROTO glue(glue(op_stl, MEMSUFFIX), _T0_A0)(void)
  62 +{
  63 + glue(stl, MEMSUFFIX)((uint8_t *)A0, T0);
  64 +}
  65 +
  66 +#undef MEMSUFFIX
... ...