when notJSnotNims:
  proc zeroMem*(p: pointer, size: Natural) {.inline, noSideEffect,
    tags: [], raises: [], enforceNoRaises.}
    ## Overwrites the contents of the memory at `p` with the value 0.
    ##
    ## Exactly `size` bytes will be overwritten. Like any procedure
    ## dealing with raw memory this is **unsafe**.

  proc copyMem*(dest, source: pointer, size: Natural) {.inline, gcsafe,
    tags: [], raises: [], enforceNoRaises.}
    ## Copies the contents from the memory at `source` to the memory
    ## at `dest`.
    ## Exactly `size` bytes will be copied. The memory
    ## regions may not overlap. Like any procedure dealing with raw
    ## memory this is **unsafe**.

  proc moveMem*(dest, source: pointer, size: Natural) {.inline, gcsafe,
    tags: [], raises: [], enforceNoRaises.}
    ## Copies the contents from the memory at `source` to the memory
    ## at `dest`.
    ##
    ## Exactly `size` bytes will be copied. The memory
    ## regions may overlap, `moveMem` handles this case appropriately
    ## and is thus somewhat more safe than `copyMem`. Like any procedure
    ## dealing with raw memory this is still **unsafe**, though.

  proc equalMem*(a, b: pointer, size: Natural): bool {.inline, noSideEffect,
    tags: [], raises: [], enforceNoRaises.}
    ## Compares the memory blocks `a` and `b`. `size` bytes will
    ## be compared.
    ##
    ## If the blocks are equal, `true` is returned, `false`
    ## otherwise. Like any procedure dealing with raw memory this is
    ## **unsafe**.

  proc cmpMem*(a, b: pointer, size: Natural): int {.inline, noSideEffect,
    tags: [], raises: [], enforceNoRaises.}
    ## Compares the memory blocks `a` and `b`. `size` bytes will
    ## be compared.
    ##
    ## Returns:
    ## * a value less than zero, if `a < b`
    ## * a value greater than zero, if `a > b`
    ## * zero, if `a == b`
    ##
    ## Like any procedure dealing with raw memory this is
    ## **unsafe**.

when hasAlloc and not defined(js):

  proc allocImpl*(size: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
  proc alloc0Impl*(size: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
  proc deallocImpl*(p: pointer) {.noconv, rtl, tags: [], gcsafe, raises: [].}
  proc reallocImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
  proc realloc0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}

  proc allocSharedImpl*(size: Natural): pointer {.noconv, compilerproc, rtl, gcsafe, raises: [], tags: [].}
  proc allocShared0Impl*(size: Natural): pointer {.noconv, rtl, gcsafe, raises: [], tags: [].}
  proc deallocSharedImpl*(p: pointer) {.noconv, rtl, gcsafe, raises: [], tags: [].}
  proc reallocSharedImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
  proc reallocShared0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}

  # Allocator statistics for memory leak tests

  {.push stackTrace: off.}

  type AllocStats* = object
    allocCount: int
    deallocCount: int

  proc `-`*(a, b: AllocStats): AllocStats =
    result.allocCount = a.allocCount - b.allocCount
    result.deallocCount = a.deallocCount - b.deallocCount

  template dumpAllocstats*(code: untyped) =
    let stats1 = getAllocStats()
    code
    let stats2 = getAllocStats()
    echo $(stats2 - stats1)

  when defined(nimAllocStats):
    var stats: AllocStats
    template incStat(what: untyped) = atomicInc stats.what
    proc getAllocStats*(): AllocStats = stats

  else:
    template incStat(what: untyped) = discard
    proc getAllocStats*(): AllocStats = result = default(AllocStats)

  template alloc*(size: Natural): pointer =
    ## Allocates a new memory block with at least `size` bytes.
    ##
    ## The block has to be freed with `realloc(block, 0) <#realloc.t,pointer,Natural>`_
    ## or `dealloc(block) <#dealloc,pointer>`_.
    ## The block is not initialized, so reading
    ## from it before writing to it is undefined behaviour!
    ##
    ## The allocated memory belongs to its allocating thread!
    ## Use `allocShared <#allocShared.t,Natural>`_ to allocate from a shared heap.
    ##
    ## See also:
    ## * `alloc0 <#alloc0.t,Natural>`_
    incStat(allocCount)
    allocImpl(size)

  proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe, raises: [].} =
    ## Allocates a new memory block with at least `T.sizeof * size` bytes.
    ##
    ## The block has to be freed with `resize(block, 0) <#resize,ptr.T,Natural>`_
    ## or `dealloc(block) <#dealloc,pointer>`_.
    ## The block is not initialized, so reading
    ## from it before writing to it is undefined behaviour!
    ##
    ## The allocated memory belongs to its allocating thread!
    ## Use `createSharedU <#createSharedU,typedesc>`_ to allocate from a shared heap.
    ##
    ## See also:
    ## * `create <#create,typedesc>`_
    cast[ptr T](alloc(T.sizeof * size))

  template alloc0*(size: Natural): pointer =
    ## Allocates a new memory block with at least `size` bytes.
    ##
    ## The block has to be freed with `realloc(block, 0) <#realloc.t,pointer,Natural>`_
    ## or `dealloc(block) <#dealloc,pointer>`_.
    ## The block is initialized with all bytes containing zero, so it is
    ## somewhat safer than  `alloc <#alloc.t,Natural>`_.
    ##
    ## The allocated memory belongs to its allocating thread!
    ## Use `allocShared0 <#allocShared0.t,Natural>`_ to allocate from a shared heap.
    incStat(allocCount)
    alloc0Impl(size)

  proc create*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe, raises: [].} =
    ## Allocates a new memory block with at least `T.sizeof * size` bytes.
    ##
    ## The block has to be freed with `resize(block, 0) <#resize,ptr.T,Natural>`_
    ## or `dealloc(block) <#dealloc,pointer>`_.
    ## The block is initialized with all bytes containing zero, so it is
    ## somewhat safer than `createU <#createU,typedesc>`_.
    ##
    ## The allocated memory belongs to its allocating thread!
    ## Use `createShared <#createShared,typedesc>`_ to allocate from a shared heap.
    cast[ptr T](alloc0(sizeof(T) * size))

  template realloc*(p: pointer, newSize: Natural): pointer =
    ## Grows or shrinks a given memory block.
    ##
    ## If `p` is **nil** then a new memory block is returned.
    ## In either way the block has at least `newSize` bytes.
    ## If `newSize == 0` and `p` is not **nil** `realloc` calls `dealloc(p)`.
    ## In other cases the block has to be freed with
    ## `dealloc(block) <#dealloc,pointer>`_.
    ##
    ## The allocated memory belongs to its allocating thread!
    ## Use `reallocShared <#reallocShared.t,pointer,Natural>`_ to reallocate
    ## from a shared heap.
    reallocImpl(p, newSize)

  template realloc0*(p: pointer, oldSize, newSize: Natural): pointer =
    ## Grows or shrinks a given memory block.
    ##
    ## If `p` is **nil** then a new memory block is returned.
    ## In either way the block has at least `newSize` bytes.
    ## If `newSize == 0` and `p` is not **nil** `realloc` calls `dealloc(p)`.
    ## In other cases the block has to be freed with
    ## `dealloc(block) <#dealloc,pointer>`_.
    ##
    ## The block is initialized with all bytes containing zero, so it is
    ## somewhat safer then realloc
    ##
    ## The allocated memory belongs to its allocating thread!
    ## Use `reallocShared <#reallocShared.t,pointer,Natural>`_ to reallocate
    ## from a shared heap.
    realloc0Impl(p, oldSize, newSize)

  proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline, gcsafe, raises: [].} =
    ## Grows or shrinks a given memory block.
    ##
    ## If `p` is **nil** then a new memory block is returned.
    ## In either way the block has at least `T.sizeof * newSize` bytes.
    ## If `newSize == 0` and `p` is not **nil** `resize` calls `dealloc(p)`.
    ## In other cases the block has to be freed with `free`.
    ##
    ## The allocated memory belongs to its allocating thread!
    ## Use `resizeShared <#resizeShared,ptr.T,Natural>`_ to reallocate
    ## from a shared heap.
    cast[ptr T](realloc(p, T.sizeof * newSize))

  proc dealloc*(p: pointer) {.noconv, compilerproc, rtl, gcsafe, raises: [], tags: [].} =
    ## Frees the memory allocated with `alloc`, `alloc0`,
    ## `realloc`, `create` or `createU`.
    ##
    ## **This procedure is dangerous!**
    ## If one forgets to free the memory a leak occurs; if one tries to
    ## access freed memory (or just freeing it twice!) a core dump may happen
    ## or other memory may be corrupted.
    ##
    ## The freed memory must belong to its allocating thread!
    ## Use `deallocShared <#deallocShared,pointer>`_ to deallocate from a shared heap.
    incStat(deallocCount)
    deallocImpl(p)

  template allocShared*(size: Natural): pointer =
    ## Allocates a new memory block on the shared heap with at
    ## least `size` bytes.
    ##
    ## The block has to be freed with
    ## `reallocShared(block, 0) <#reallocShared.t,pointer,Natural>`_
    ## or `deallocShared(block) <#deallocShared,pointer>`_.
    ##
    ## The block is not initialized, so reading from it before writing
    ## to it is undefined behaviour!
    ##
    ## See also:
    ## * `allocShared0 <#allocShared0.t,Natural>`_.
    incStat(allocCount)
    allocSharedImpl(size)

  proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline, tags: [],
                                                               gcsafe, raises: [].} =
    ## Allocates a new memory block on the shared heap with at
    ## least `T.sizeof * size` bytes.
    ##
    ## The block has to be freed with
    ## `resizeShared(block, 0) <#resizeShared,ptr.T,Natural>`_ or
    ## `freeShared(block) <#freeShared,ptr.T>`_.
    ##
    ## The block is not initialized, so reading from it before writing
    ## to it is undefined behaviour!
    ##
    ## See also:
    ## * `createShared <#createShared,typedesc>`_
    cast[ptr T](allocShared(T.sizeof * size))

  template allocShared0*(size: Natural): pointer =
    ## Allocates a new memory block on the shared heap with at
    ## least `size` bytes.
    ##
    ## The block has to be freed with
    ## `reallocShared(block, 0) <#reallocShared.t,pointer,Natural>`_
    ## or `deallocShared(block) <#deallocShared,pointer>`_.
    ##
    ## The block is initialized with all bytes
    ## containing zero, so it is somewhat safer than
    ## `allocShared <#allocShared.t,Natural>`_.
    incStat(allocCount)
    allocShared0Impl(size)

  proc createShared*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
    ## Allocates a new memory block on the shared heap with at
    ## least `T.sizeof * size` bytes.
    ##
    ## The block has to be freed with
    ## `resizeShared(block, 0) <#resizeShared,ptr.T,Natural>`_ or
    ## `freeShared(block) <#freeShared,ptr.T>`_.
    ##
    ## The block is initialized with all bytes
    ## containing zero, so it is somewhat safer than
    ## `createSharedU <#createSharedU,typedesc>`_.
    cast[ptr T](allocShared0(T.sizeof * size))

  template reallocShared*(p: pointer, newSize: Natural): pointer =
    ## Grows or shrinks a given memory block on the heap.
    ##
    ## If `p` is **nil** then a new memory block is returned.
    ## In either way the block has at least `newSize` bytes.
    ## If `newSize == 0` and `p` is not **nil** `reallocShared` calls
    ## `deallocShared(p)`.
    ## In other cases the block has to be freed with
    ## `deallocShared <#deallocShared,pointer>`_.
    reallocSharedImpl(p, newSize)

  template reallocShared0*(p: pointer, oldSize, newSize: Natural): pointer =
    ## Grows or shrinks a given memory block on the heap.
    ##
    ## When growing, the new bytes of the block is initialized with all bytes
    ## containing zero, so it is somewhat safer then reallocShared
    ##
    ## If `p` is **nil** then a new memory block is returned.
    ## In either way the block has at least `newSize` bytes.
    ## If `newSize == 0` and `p` is not **nil** `reallocShared` calls
    ## `deallocShared(p)`.
    ## In other cases the block has to be freed with
    ## `deallocShared <#deallocShared,pointer>`_.
    reallocShared0Impl(p, oldSize, newSize)

  proc resizeShared*[T](p: ptr T, newSize: Natural): ptr T {.inline, raises: [].} =
    ## Grows or shrinks a given memory block on the heap.
    ##
    ## If `p` is **nil** then a new memory block is returned.
    ## In either way the block has at least `T.sizeof * newSize` bytes.
    ## If `newSize == 0` and `p` is not **nil** `resizeShared` calls
    ## `freeShared(p)`.
    ## In other cases the block has to be freed with
    ## `freeShared <#freeShared,ptr.T>`_.
    cast[ptr T](reallocShared(p, T.sizeof * newSize))

  proc deallocShared*(p: pointer) {.noconv, compilerproc, rtl, gcsafe, raises: [], tags: [].} =
    ## Frees the memory allocated with `allocShared`, `allocShared0` or
    ## `reallocShared`.
    ##
    ## **This procedure is dangerous!**
    ## If one forgets to free the memory a leak occurs; if one tries to
    ## access freed memory (or just freeing it twice!) a core dump may happen
    ## or other memory may be corrupted.
    incStat(deallocCount)
    deallocSharedImpl(p)

  proc freeShared*[T](p: ptr T) {.inline, gcsafe, raises: [].} =
    ## Frees the memory allocated with `createShared`, `createSharedU` or
    ## `resizeShared`.
    ##
    ## **This procedure is dangerous!**
    ## If one forgets to free the memory a leak occurs; if one tries to
    ## access freed memory (or just freeing it twice!) a core dump may happen
    ## or other memory may be corrupted.
    deallocShared(p)

  include bitmasks

  proc alignedAlloc(size, align: Natural): pointer =
    if align <= MemAlign:
      when compileOption("threads"):
        result = allocShared(size)
      else:
        result = alloc(size)
    else:
      # allocate (size + align - 1) necessary for alignment,
      # plus 2 bytes to store offset
      let base =
        when compileOption("threads"):
          allocShared(cast[Natural](size +% align -% 1 +% sizeof(uint16)))
        else:
          alloc(cast[Natural](size +% align -% 1 +% sizeof(uint16)))
      # memory layout: padding + offset (2 bytes) + user_data
      # in order to deallocate: read offset at user_data - 2 bytes,
      # then deallocate user_data - offset
      let offset = align -% cast[int](cast[uint](base) and uint(align -% 1))
      result = base +! offset
      cast[ptr uint16](result -! sizeof(uint16))[] = uint16(offset)

  proc alignedAlloc0(size, align: Natural): pointer =
    result = alignedAlloc(size, align)
    zeroMem(result, size)

  proc alignedDealloc(p: pointer, align: int) {.compilerproc.} =
    if align <= MemAlign:
      when compileOption("threads"):
        deallocShared(p)
      else:
        dealloc(p)
    else:
      # read offset at p - 2 bytes, then deallocate (p - offset) pointer
      let offset = cast[ptr uint16](p -! sizeof(uint16))[]
      when compileOption("threads"):
        deallocShared(p -! offset)
      else:
        dealloc(p -! offset)

  proc alignedRealloc(p: pointer, oldSize, newSize, align: Natural): pointer =
    if align <= MemAlign:
      when compileOption("threads"):
        result = reallocShared(p, newSize)
      else:
        result = realloc(p, newSize)
    else:
      result = alignedAlloc(newSize, align)
      copyMem(result, p, oldSize)
      alignedDealloc(p, align)

  proc alignedRealloc0(p: pointer, oldSize, newSize, align: Natural): pointer =
    if align <= MemAlign:
      when compileOption("threads"):
        result = reallocShared0(p, oldSize, newSize)
      else:
        result = realloc0(p, oldSize, newSize)
    else:
      result = alignedAlloc(newSize, align)
      copyMem(result, p, oldSize)
      zeroMem(result +! oldSize, newSize -% oldSize)
      alignedDealloc(p, align)

  {.pop.}

# GC interface:

when hasAlloc:
  proc getOccupiedMem*(): int {.rtl.}
    ## Returns the number of bytes that are owned by the process and hold data.

  proc getFreeMem*(): int {.rtl.}
    ## Returns the number of bytes that are owned by the process, but do not
    ## hold any meaningful data.

  proc getTotalMem*(): int {.rtl.}
    ## Returns the number of bytes that are owned by the process.


when defined(js):
  # Stubs:
  proc getOccupiedMem(): int = return -1
  proc getFreeMem(): int = return -1
  proc getTotalMem(): int = return -1

  proc dealloc(p: pointer) = discard
  proc alloc(size: Natural): pointer = discard
  proc alloc0(size: Natural): pointer = discard
  proc realloc(p: pointer, newsize: Natural): pointer = discard
  proc realloc0(p: pointer, oldsize, newsize: Natural): pointer = discard

  proc allocShared(size: Natural): pointer = discard
  proc allocShared0(size: Natural): pointer = discard
  proc deallocShared(p: pointer) = discard
  proc reallocShared(p: pointer, newsize: Natural): pointer = discard
  proc reallocShared0(p: pointer, oldsize, newsize: Natural): pointer = discard


when hasAlloc and hasThreadSupport and not defined(useMalloc):
  proc getOccupiedSharedMem*(): int {.rtl.}
    ## Returns the number of bytes that are owned by the process
    ## on the shared heap and hold data. This is only available when
    ## threads are enabled.

  proc getFreeSharedMem*(): int {.rtl.}
    ## Returns the number of bytes that are owned by the
    ## process on the shared heap, but do not hold any meaningful data.
    ## This is only available when threads are enabled.

  proc getTotalSharedMem*(): int {.rtl.}
    ## Returns the number of bytes on the shared heap that are owned by the
    ## process. This is only available when threads are enabled.
