Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make js code correct on little + big endian #515

Open
timotheecour opened this issue Jan 9, 2021 · 3 comments
Open

make js code correct on little + big endian #515

timotheecour opened this issue Jan 9, 2021 · 3 comments

Comments

@timotheecour
Copy link
Owner

timotheecour commented Jan 9, 2021

some js code in nim (eg nim-lang#16592) uses implicit assumption of little endian; this is likely true for most platforms (even more so for js code) but still, it's not always correct.

example

when defined(js):
  proc toBitsImpl(x: float): array[2, uint32] =
    asm """
    const buffer = new ArrayBuffer(8);
    const floatBuffer = new Float64Array(buffer);
    const uintBuffer = new Uint32Array(buffer);
    floatBuffer[0] = `x`;
    `result` = uintBuffer
    """

proposal

use this (or better) from https://stackoverflow.com/questions/7869752/javascript-typed-arrays-and-endianness

const isBigEndian = (() => {
    const array = new Uint8Array(4);
    const view = new Uint32Array(array.buffer);
    return !((view[0] = 1) & array[0]);
})();

then adapt code depending on isBigEndian

Additional Information

1.5.1 d34d023
low priority because big endian is not a common thing in js

/cc @xflywind @juancarlospaco

links

https://riptutorial.com/javascript/example/13317/little---big-endian-for-typed-arrays-when-using-bitwise-operators see Example where Edian type is important

let's assume littleEndian in JS backend by xflywind · Pull Request #16886 · nim-lang/Nim

make endians support JS backend by xflywind · Pull Request #16127 · nim-lang/Nim

endian independent views

https://stackoverflow.com/questions/63179879/should-i-care-about-big-endian-machines-while-using-uint32array

If you're concerned about endianness, you can use the DataView interface. This allows you to assert the endianness of a byte array, rather than relying on the platform byte order.

big endian machines

https://stackoverflow.com/questions/63179879/should-i-care-about-big-endian-machines-while-using-uint32array

This may be a concern if you expect your code to run on a PowerPC platform like an XBox or PS3.

@timotheecour
Copy link
Owner Author

timotheecour commented Feb 12, 2021

swap endian-ness (to add support for std/endian in js):

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32

littleEndian
Optional Indicates whether the 32-bit int is stored in little- or big-endian format. If false or undefined, a big-endian value is read.

eg how to use it: https://www.i-programmer.info/programming/javascript/6151-javascript-data-structures-typed-arrays-ii.html?start=1

If you have a Big-endian array of two byte integers and want to work with it on a Little-endian machine you can use the equivalent idea for an array:

other links

https://exploringjs.com/impatient-js/ch_typed-arrays.html
https://stackoverflow.com/questions/5320439/how-do-i-swap-endian-ness-byte-order-of-a-variable-in-javascript#comment34167914_5320624

nodejs

https://nodejs.org/api/buffer.html#buffer_buf_swap16

@ringabout
Copy link
Collaborator

The full codebase

when defined(js):
  import std/jsbigints

  type
    ArrayBuffer* = ref object of JsRoot
    Float64Array* = ref object of JsRoot
    Uint32Array* = ref object of JsRoot
    BigUint64Array* = ref object of JsRoot

  func newArrayBuffer*(n: int): ArrayBuffer {.importjs: "new ArrayBuffer(#)".}
  func newFloat64Array*(buffer: ArrayBuffer): Float64Array {.importjs: "new Float64Array(#)".}
  func newUint32Array*(buffer: ArrayBuffer): Uint32Array {.importjs: "new Uint32Array(#)".}
  func newBigUint64Array*(buffer: ArrayBuffer): BigUint64Array {.importjs: "new BigUint64Array(#)".}

  func `[]`*(arr: Uint32Array, i: int): uint32 {.importjs: "#[#]".}
  func `[]`*(arr: BigUint64Array, i: int): JsBigInt {.importjs: "#[#]".}
  func `[]=`*(arr: Float64Array, i: int, v: float) {.importjs: "#[#] = #".}

  proc hasJsBigInt*(): bool =
    asm """`result` = typeof BigInt != 'undefined'"""

when defined(js):
  type
    Uint16Array* = ref object of JsRoot
    Uint8Array* = ref object of JsRoot

  func buffer(p: Uint32Array): ArrayBuffer {.importjs: "#.buffer".}

  func newUint32Array*(arr: openArray[uint32]): Uint32Array {.importjs: "new Uint32Array(#)".}

  func newUint8Array(p: ArrayBuffer): Uint8Array {.importjs: "new Uint8Array(#)".}

  func `[]`*(arr: Uint8Array, i: int): uint8 {.importjs: "#[#]".}


when defined(js):
  when defined(nodejs):
    {.emit: "const _nim_nodejs_os = require('os');".}

    proc endianness(): cstring {.importjs: "_nim_nodejs_os.endianness()".}

    template getEndiansJS(): Endianness =
      if endianness() == "LE":
        littleEndian
      else:
        bigEndian
  else:
    template getEndiansJS(): Endianness =
      let a = newUint32Array([0x12345678'u32])
      let b = newUint8Array(a.buffer)
      if b[0] == 0x78:
        littleEndian
      else:
        bigEndian

template getEndiansVM(): Endianness =
  when defined(i386) or defined(alpha) or defined(powerpc64el) or
      defined(ia64) or defined(amd64) or defined(mipsel) or defined(arm) or
      defined(arm64) or defined(avr) or defined(msp430) or defined(riscv32) or
      defined(riscv64) or defined(esp) or defined(wasm32) or defined(mips64el):
    littleEndian
  elif defined(m68k) or defined(powerpc) or defined(powerpc64) or defined(sparc) or
      defined(hppa) or defined(mips) or defined(sparc64) or defined(mips64):
    bigEndian
  else:
    littleEndian

proc getEndians(): Endianness {.inline.} =
  when nimvm:
    getEndiansVM()
  else:
    when defined(js):
      result = getEndiansJS()
    else:
      result = cpuEndian

const cpuEndiansVM* = getEndians()
let cpuEndiansRT* = getEndians()


#
#
#            Nim's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module contains helpers that deal with different byte orders
## (`endian`:idx:).
##
## Unstable API.
when not defined(js):
  template builtin_bswap(a: uint8): uint8 =
    a
  when defined(gcc) or defined(llvm_gcc) or defined(clang):
    func builtin_bswap(a: uint16): uint16 {.
        importc: "__builtin_bswap16", nodecl.}

    func builtin_bswap(a: uint32): uint32 {.
        importc: "__builtin_bswap32", nodecl.}

    func builtin_bswap(a: uint64): uint64 {.
        importc: "__builtin_bswap64", nodecl.}
  elif defined(icc):
    func builtin_bswap(a: uint16): uint16 {.
        importc: "_bswap16", nodecl.}

    func builtin_bswap(a: uint32): uint32 {.
        importc: "_bswap", nodecl.}

    func builtin_bswap(a: uint64): uint64 {.
        importc: "_bswap64", nodecl.}
  elif defined(vcc):
    func builtin_bswap(a: uint16): uint16 {.
        importc: "_byteswap_ushort", nodecl, header: "<intrin.h>".}

    func builtin_bswap(a: uint32): uint32 {.
        importc: "_byteswap_ulong", nodecl, header: "<intrin.h>".}

    func builtin_bswap(a: uint64): uint64 {.
        importc: "_byteswap_uint64", nodecl, header: "<intrin.h>".}

template builtin_bswapImpl(a: uint8): uint8 =
  a

template builtin_bswapImpl(a: uint16): uint16 =
  (a shl 8'u16) or (a shr 8'u16)

template builtin_bswapImpl(a: uint32): uint32 =
  ((a shl 24) and 0xff000000'u32) or
            ((a shl 8) and 0x00ff0000'u32) or
            ((a shr 8) and 0x0000ff00'u32) or
            ((a shr 24) and 0x000000ff'u32)

template builtin_bswapImpl(a: uint64): uint64 =
  var num = (a shl 32'u64) or (a shr 32'u64)
  num = ((num and 0x0000ffff0000ffff'u64) shl 16'u64) or
      ((num and 0xffff0000ffff0000'u64) shr 16'u64)

  ((num and 0x00ff00ff00ff00ff'u64) shl 8'u64) or
            ((num and 0xff00ff00ff00ff00'u64) shr 8'u64)

func swapEndian*[T: SomeUnsignedInt](a: T): T {.inline.} =
  when nimvm:
    result = builtin_bswapImpl(a)
  else:
    when defined(js):
      result = builtin_bswapImpl(a)
    else:
      result = builtin_bswap(a)

proc toEndian[T: SomeUnsignedInt](a: T, endianess: static Endianness): T {.inline.} =
  when nimvm:
    when cpuEndiansVM == endianess:
      result = swapEndian(a)
    else:
      result = a
  else:
    when defined(js):
      if cpuEndiansRT == endianess:
        result = swapEndian(a)
      else:
        result = a
    else:
      when cpuEndiansVM == endianess:
        result = swapEndian(a)
      else:
        result = a

proc toBigEndian*[T: SomeUnsignedInt](a: T): T {.inline.} =
  result = toEndian(a, littleEndian)

proc toLittleEndian*[T: SomeUnsignedInt](a: T): T {.inline.} =
  result = toEndian(a, bigEndian)


proc main() =
  doAssert toLittleEndian(12'u32) == 12
  doAssert toLittleEndian(12'u64) == 12
  doAssert toLittleEndian(12'u16) == 12
  doAssert toLittleEndian(12'u8) == 12

  echo toBigEndian(12'u64)
  doAssert toBigEndian(12'u32) == 201326592
  doAssert toBigEndian(12'u16) == 3072
  doAssert toBigEndian(12'u8) == 12


static: main()
main()

@timotheecour
Copy link
Owner Author

seems like a good start! is builtin_bswapImpl correct in nim js? eg for the whole range of uint32?
( i was expecting dataview.getUint32(byteOffset [, littleEndian]) would be needed for this, but maybe I'm wrong)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants