@socketsupply/socket
Version:
A Cross-Platform, Native Runtime for Desktop and Mobile Apps — Create apps using HTML, CSS, and JavaScript. Written from the ground up to be small and maintainable.
1,978 lines (1,672 loc) • 137 kB
JavaScript
/* global Event, ErrorEvent */
/* eslint-disable no-unused-vars */
/**
* @module extension
*
* @example
* import extension from 'socket:extension'
* const ex = await extension.load('native_extension')
* const result = ex.binding.method('argument')
*/
import { isBufferLike } from './util.js'
import { createFile } from './fs/web.js'
import application from './application.js'
import process from './process.js'
import crypto from './crypto.js'
import path from './path.js'
import ipc from './ipc.js'
import fs from './fs/promises.js'
/**
* @typedef {number} Pointer
*/
const $loaded = Symbol('loaded')
const $stats = Symbol('stats')
const $type = Symbol('type')
const NULL = 0x0
const EOF = -1
// eslint-disable-next-line
const STDIN = 0x0 // STDIN_FILENO
const STDOUT = 0x1 // STDOUT_FILENO
const STDERR = 0x2 // STDERR_FILNO
const EPSILON = 1.1920928955078125e-07
const EXIT_FAILURE = 1
const EXIT_SUCCESS = 0
const RAND_MAX = (0x7fffffff)
const PATH_MAX = 4096
const CLOCKS_PER_SEC = 1000
const TIME_UTC = 1
const TIMER_ABSTIME = 1
const CLOCK_REALTIME = 0
const CLOCK_MONOTONIC = 1
const CLOCK_PROCESS_CPUTIME_ID = 2
const CLOCK_THREAD_CPUTIME_ID = 3
const CLOCK_MONOTONIC_RAW = 4
const CLOCK_REALTIME_COARSE = 5
const CLOCK_MONOTONIC_COARSE = 6
const CLOCK_BOOTTIME = 7
const CLOCK_REALTIME_ALARM = 8
const CLOCK_BOOTTIME_ALARM = 9
const CLOCK_SGI_CYCLE = 10
const CLOCK_TAI = 11
const EPERM = 1
const ENOENT = 2
const ESRCH = 3
const EINTR = 4
const EIO = 5
const ENXIO = 6
const E2BIG = 7
const ENOEXEC = 8
const EBADF = 9
const ECHILD = 10
const EAGAIN = 11
const ENOMEM = 12
const EACCES = 13
const EFAULT = 14
const ENOTBLK = 15
const EBUSY = 16
const EEXIST = 17
const EXDEV = 18
const ENODEV = 19
const ENOTDIR = 20
const EISDIR = 21
const EINVAL = 22
const ENFILE = 23
const EMFILE = 24
const ENOTTY = 25
const ETXTBSY = 26
const EFBIG = 27
const ENOSPC = 28
const ESPIPE = 29
const EROFS = 30
const EMLINK = 31
const EPIPE = 32
const EDOM = 33
const ERANGE = 34
const EDEADLK = 35
const ENAMETOOLONG = 36
const ENOLCK = 37
const ENOSYS = 38
const ENOTEMPTY = 39
const ELOOP = 40
const EWOULDBLOCK = EAGAIN
const ENOMSG = 42
const EIDRM = 43
const ECHRNG = 44
const EL2NSYNC = 45
const EL3HLT = 46
const EL3RST = 47
const ELNRNG = 48
const EUNATCH = 49
const ENOCSI = 50
const EL2HLT = 51
const EBADE = 52
const EBADR = 53
const EXFULL = 54
const ENOANO = 55
const EBADRQC = 56
const EBADSLT = 57
const EDEADLOCK = EDEADLK
const EBFONT = 59
const ENOSTR = 60
const ENODATA = 61
const ETIME = 62
const ENOSR = 63
const ENONET = 64
const ENOPKG = 65
const EREMOTE = 66
const ENOLINK = 67
const EADV = 68
const ESRMNT = 69
const ECOMM = 70
const EPROTO = 71
const EMULTIHOP = 72
const EDOTDOT = 73
const EBADMSG = 74
const EOVERFLOW = 75
const ENOTUNIQ = 76
const EBADFD = 77
const EREMCHG = 78
const ELIBACC = 79
const ELIBBAD = 80
const ELIBSCN = 81
const ELIBMAX = 82
const ELIBEXEC = 83
const EILSEQ = 84
const ERESTART = 85
const ESTRPIPE = 86
const EUSERS = 87
const ENOTSOCK = 88
const EDESTADDRREQ = 89
const EMSGSIZE = 90
const EPROTOTYPE = 91
const ENOPROTOOPT = 92
const EPROTONOSUPPORT = 93
const ESOCKTNOSUPPORT = 94
const EOPNOTSUPP = 95
const ENOTSUP = EOPNOTSUPP
const EPFNOSUPPORT = 96
const EAFNOSUPPORT = 97
const EADDRINUSE = 98
const EADDRNOTAVAIL = 99
const ENETDOWN = 100
const ENETUNREACH = 101
const ENETRESET = 102
const ECONNABORTED = 103
const ECONNRESET = 104
const ENOBUFS = 105
const EISCONN = 106
const ENOTCONN = 107
const ESHUTDOWN = 108
const ETOOMANYREFS = 109
const ETIMEDOUT = 110
const ECONNREFUSED = 111
const EHOSTDOWN = 112
const EHOSTUNREACH = 113
const EALREADY = 114
const EINPROGRESS = 115
const ESTALE = 116
const EUCLEAN = 117
const ENOTNAM = 118
const ENAVAIL = 119
const EISNAM = 120
const EREMOTEIO = 121
const EDQUOT = 122
const ENOMEDIUM = 123
const EMEDIUMTYPE = 124
const ECANCELED = 125
const ENOKEY = 126
const EKEYEXPIRED = 127
const EKEYREVOKED = 128
const EKEYREJECTED = 129
const EOWNERDEAD = 130
const ENOTRECOVERABLE = 131
const ERFKILL = 132
const EHWPOISON = 133
const SAPI_JSON_TYPE_EMPTY = -1
const SAPI_JSON_TYPE_ANY = 0
const SAPI_JSON_TYPE_NULL = 1
const SAPI_JSON_TYPE_OBJECT = 2
const SAPI_JSON_TYPE_ARRAY = 3
const SAPI_JSON_TYPE_BOOLEAN = 4
const SAPI_JSON_TYPE_NUMBER = 5
const SAPI_JSON_TYPE_STRING = 6
const SAPI_JSON_TYPE_RAW = 7
const REG_EXTENDED = 0x1
const REG_ICASE = 0x2
const REG_NOSUB = 0x4
const REG_NEWLINE = 0x8
const REG_NOMATCH = 1
const REG_BADPAT = 2
const REG_ECOLLATE = 3
const REG_ECTYPE = 4
const REG_EESCAPE = 5
const REG_ESUBREG = 6
const REG_EBRACK = 7
const REG_EPAREN = 8
const REG_EBRACE = 9
const REG_BADBR = 10
const REG_ERANGE = 11
const REG_ESPACE = 12
const REG_BADRPT = 13
const REG_ENOSYS = 14
const REG_NOTBOL = 0x1
const REG_NOTEOL = 0x2
const errorMessages = {
0: 'Undefined error: 0',
[EPERM]: 'Operation not permitted',
[ENOENT]: 'No such file or directory',
[ESRCH]: 'No such process',
[EINTR]: 'Interrupted system call',
[EIO]: 'Input/output error',
[ENXIO]: 'Device not configured',
[E2BIG]: 'Argument list too long',
[ENOEXEC]: 'Exec format error',
[EBADF]: 'Bad file descriptor',
[ECHILD]: 'No child processes',
[EAGAIN]: 'Try again',
[ENOMEM]: 'Out of memory',
[EACCES]: 'Permission denied',
[EFAULT]: 'Bad address',
[ENOTBLK]: 'Block device required',
[EBUSY]: 'Device or resource busy',
[EEXIST]: 'File exists',
[EXDEV]: 'Cross-device link',
[ENODEV]: 'No such device',
[ENOTDIR]: 'Not a directory',
[EISDIR]: 'Is a directory',
[EINVAL]: 'Invalid argument',
[ENFILE]: 'File table overflow',
[EMFILE]: 'Too many open files',
[ENOTTY]: 'Not a typewriter',
[ETXTBSY]: 'Text file busy',
[EFBIG]: 'File too large',
[ENOSPC]: 'No space left on device',
[ESPIPE]: 'Illegal seek',
[EROFS]: 'Read-only file system ',
[EMLINK]: 'Too many links',
[EPIPE]: 'Broken pipe',
[EDOM]: ' Numerical argument out of domain',
[ERANGE]: 'umerical result out of range',
[EDEADLK]: 'Resource deadlock avoided',
[ENAMETOOLONG]: 'File name too long',
[ENOLCK]: 'No locks available',
[ENOSYS]: 'Function not implemented',
[ENOTEMPTY]: 'Directory not empty',
[ELOOP]: 'Too many levels of symbolic links',
[EWOULDBLOCK]: 'Try again',
[ENOMSG]: 'No message of desired type',
[EIDRM]: 'Identifier removed',
[ECHRNG]: 'Character range error',
[EL2NSYNC]: 'Level 2 not synchronized',
[EL3HLT]: 'Level 3 halted',
[EL3RST]: 'EL3RST',
[ELNRNG]: 'ELNRNG',
[EUNATCH]: 'No such device',
[ENOCSI]: 'No CSI structure available',
[EL2HLT]: 'Level 2 halted',
[EBADE]: 'Invalid exchange',
[EBADR]: 'Invalid request descriptor',
[EXFULL]: 'Exchange full',
[ENOANO]: 'No anode',
[EBADRQC]: 'Invalid request code',
[EBADSLT]: 'Invalid slot',
[EDEADLOCK]: 'Resource deadlock avoided',
[EBFONT]: 'Bad font file format',
[ENOSTR]: 'Device not a stream',
[ENODATA]: 'No data available',
[ETIME]: 'Timer expired',
[ENOSR]: 'Out of streams resources',
[ENONET]: 'Machine is not on the network',
[ENOPKG]: 'Package not installed',
[EREMOTE]: 'Object is remote',
[ENOLINK]: 'Link has been severed',
[EADV]: 'Advertise error',
[ESRMNT]: 'Srmount error',
[ECOMM]: 'Communication error on send',
[EPROTO]: 'Protocol error',
[EMULTIHOP]: 'Multihop attempted',
[EDOTDOT]: 'RFS specific error',
[EBADMSG]: 'Not a data message',
[EOVERFLOW]: 'Value too large for defined data type',
[ENOTUNIQ]: 'Name not unique on network',
[EBADFD]: 'File descriptor in bad state',
[EREMCHG]: 'Remote address changed',
[ELIBACC]: 'Can not access a needed shared library',
[ELIBBAD]: 'Accessing a corrupted shared library',
[ELIBSCN]: '.lib section in a.out corrupted',
[ELIBMAX]: 'Attempting to link in too many shared libraries',
[ELIBEXEC]: 'Cannot exec a shared library directly',
[EILSEQ]: 'Illegal byte sequence',
[ERESTART]: 'Interrupted system call should be restarted',
[ESTRPIPE]: 'Streams pipe error',
[EUSERS]: 'Too many users',
[ENOTSOCK]: 'Socket operation on non-socket',
[EDESTADDRREQ]: 'Destination address required',
[EMSGSIZE]: 'Message too long',
[EPROTOTYPE]: 'Protocol wrong type for socket',
[ENOPROTOOPT]: 'Protocol not available',
[EPROTONOSUPPORT]: 'Protocol not supported',
[ESOCKTNOSUPPORT]: 'Socket type not supported',
[EOPNOTSUPP]: 'Operation not supported',
[ENOTSUP]: 'Operation not supported',
[EPFNOSUPPORT]: 'Protocol family not supported',
[EAFNOSUPPORT]: 'Address family not supported by protocol',
[EADDRINUSE]: 'Address already in use',
[EADDRNOTAVAIL]: 'Cannot assign requested address',
[ENETDOWN]: 'Network is down',
[ENETUNREACH]: 'Network is unreachable',
[ENETRESET]: 'Network dropped connection because of reset',
[ECONNABORTED]: 'Software caused connection abort',
[ECONNRESET]: 'Connection reset by peer',
[ENOBUFS]: 'No buffer space available',
[EISCONN]: 'Transport endpoint is already connected',
[ENOTCONN]: 'Transport endpoint is not connected',
[ESHUTDOWN]: 'Cannot send after transport endpoint shutdown',
[ETOOMANYREFS]: 'Too many references: cannot splice',
[ETIMEDOUT]: 'Connection timed out',
[ECONNREFUSED]: 'Connection refused',
[EHOSTDOWN]: 'Host is down',
[EHOSTUNREACH]: 'No route to host',
[EALREADY]: 'Operation already in progress',
[EINPROGRESS]: 'Operation now in progress',
[ESTALE]: 'Stale NFS file handle',
[EUCLEAN]: 'Structure needs cleaning',
[ENOTNAM]: 'Not a XENIX named type file',
[ENAVAIL]: 'No XENIX semaphores available',
[EISNAM]: 'Is a named type file',
[EREMOTEIO]: 'Remote I/O error',
[EDQUOT]: 'Quota exceeded',
[ENOMEDIUM]: 'No medium found',
[EMEDIUMTYPE]: 'Wrong medium type',
[ECANCELED]: 'Operation canceled',
[ENOKEY]: 'Required key not available',
[EKEYEXPIRED]: 'Key has expired',
[EKEYREVOKED]: 'Key has been revoked',
[EKEYREJECTED]: 'Key was rejected by service',
[EOWNERDEAD]: 'Owner died',
[ENOTRECOVERABLE]: 'State not recoverable',
[ERFKILL]: 'Operation not possible due to RF-kill',
[EHWPOISON]: 'Memory page has hardware error'
}
class WebAssemblyExtensionRuntimeObject {
static get pointerSize () { return 4 }
adapter = null
constructor (adapter) {
this.adapter = adapter
}
}
class WebAssemblyExtensionRuntimeBuffer extends WebAssemblyExtensionRuntimeObject {
context = null
pointer = NULL
#buffer = new Uint8Array(0)
#bufferPointer = NULL
constructor (adapter, context) {
super(adapter)
this.context = context
this.pointer = context.createExternalReferenceValue(this)
}
get buffer () {
return this.#buffer
}
get bufferPointer () {
return this.#bufferPointer
}
set (pointer, size) {
this.#buffer = new Uint8Array(size)
this.#buffer.fill(0)
this.#buffer.set(this.adapter.get(pointer, size))
this.#bufferPointer = pointer
}
}
class WebAssemblyExtensionRuntimeJSON extends WebAssemblyExtensionRuntimeObject {
context = null
pointer = NULL
#value = null
constructor (adapter, context, value) {
super(adapter)
this.context = context
this.pointer = context.createExternalReferenceValue(this)
this.value = value
}
set value (value) {
this.#value = value
}
get value () {
return this.#value
}
}
class WebAssemblyExtensionRuntimeIPCResultJSON extends WebAssemblyExtensionRuntimeJSON {
#data = null
#err = null
constructor (adapter, context, value) {
super(adapter, context, value)
this.#data = new WebAssemblyExtensionRuntimeJSON(adapter, context, this.value?.data ?? null)
this.#err = new WebAssemblyExtensionRuntimeJSON(adapter, context, this.value?.err ?? null)
}
set value (value) {
if (value?.data) {
this.#data.value = value.data
}
if (value?.err) {
this.#err.value = value.err
}
super.value = value
}
get value () {
return super.value
}
get data () {
return this.#data
}
get err () {
return this.#err
}
}
class WebAssemblyExtensionRuntimeIPCResultHeaders extends WebAssemblyExtensionRuntimeObject {
#headers = new ipc.Headers()
#pointerMap = new Map()
context = null
pointer = NULL
constructor (adapter, context) {
super(adapter)
this.context = context
this.pointer = context.createExternalReferenceValue(this)
}
get value () {
return this.#headers
}
get pointerMap () {
return this.#pointerMap
}
set (name, value) {
this.#headers.set(name, value)
this.#pointerMap.set(name, this.context.createExternalReferenceValue(value))
}
get (name) {
return this.#headers.get(name)
}
getValuePointer (name) {
return this.#pointerMap.get(name)
}
}
class WebAssemblyExtensionRuntimeIPCMessage extends WebAssemblyExtensionRuntimeObject {
context = null
pointer = NULL
#id = null
#name = null
#seq = null
#uri = null
#value = null
#values = {}
#bytes = null
#message = null
constructor (adapter, context, url, ...args) {
super(adapter)
this.context = context
this.pointer = context.createExternalReferenceValue(this)
this.#message = ipc.Message.from(url, ...args)
this.#id = new WebAssemblyExtensionRuntimeJSON(adapter, context, this.#message.id)
this.#name = new WebAssemblyExtensionRuntimeJSON(adapter, context, this.#message.name)
this.#uri = new WebAssemblyExtensionRuntimeJSON(adapter, context, this.#message.toString())
this.#seq = new WebAssemblyExtensionRuntimeJSON(adapter, context, this.#message.seq)
this.#value = new WebAssemblyExtensionRuntimeJSON(adapter, context, this.#message.value)
this.#bytes = new WebAssemblyExtensionRuntimeBuffer(adapter, context, this.#message.bytes)
for (const key of this.#message.searchParams.keys()) {
const value = this.#message.get(key)
this.#values[key] = new WebAssemblyExtensionRuntimeJSON(adapter, context, value)
}
}
get id () { return this.#id }
get name () { return this.#name }
get uri () { return this.#uri }
get seq () { return this.#seq }
get index () { return this.#message.index }
get value () { return this.#value }
get bytes () { return this.#bytes }
get (key) {
return this.#values[key]
}
}
class WebAssemblyExtensionRuntimeIPCResult extends WebAssemblyExtensionRuntimeObject {
#message = null
#bytes = null
#headers = null
#json = null
context = null
pointer = NULL
result = null
source = null
seq = null
id = null
constructor (adapter, context, message, existingResult = null) {
super(adapter)
this.#json = new WebAssemblyExtensionRuntimeIPCResultJSON(adapter, context, {})
this.#bytes = new WebAssemblyExtensionRuntimeBuffer(adapter, context)
this.#headers = new WebAssemblyExtensionRuntimeIPCResultHeaders(adapter, context)
this.pointer = context.createExternalReferenceValue(this)
this.context = context
this.message = message
this.source = message.name
if (this.result && existingResult) {
this.id = existingResult.id
this.seq = existingResult.seq
this.err = existingResult.err
this.data = existingResult.data
this.source = existingResult.source
for (const entry of existingResult.pointerMap.entries()) {
this.#headers.pointerMap.set(entry[0], entry[1])
}
for (const entry of existingResult.headers.entries()) {
this.#headers.value.set(entry[0], entry[1])
}
}
}
set message (message) {
this.#message = message
this.seq = message.seq
this.source = message.name
this.result = ipc.Result.from(
{ id: message.id },
null,
message.name,
message.headers
)
}
get message () {
return this.#message
}
get headers () {
return this.#headers
}
get bytes () {
return this.#bytes
}
set data (data) {
this.#json.data.value = data ?? null
}
get data () {
return this.#json.data
}
// eslint-disable-next-line
set err (err) {
this.#json.err.value = err ?? null
}
get err () {
return this.#json.err
}
set json (json) {
this.#json.value = json
this.source = json?.source ?? this.source
this.data = json?.data ?? this.data
this.err = json?.err ?? this.err
this.id = json?.id ?? this.id
}
get json () {
return this.#json
}
toJSON () {
return this.#json.value
}
reply () {
if (!this.context) {
this.result = null
return false
}
const callback = this.context.internal?.callback
this.result.headers = this.headers.value
this.result.source = this.source.value
this.result.err = this.err.value ?? null
this.result.id = this.id || ''
if (this.bytes.buffer.byteLength > 0) {
this.result.data = this.bytes.buffer
} else {
this.result.data = this.data.value ?? this.json.value
}
if (typeof callback === 'function') {
callback(this.result)
}
this.context.release()
this.result = null
this.context = null
return typeof callback === 'function'
}
}
class WebAssemblyExtensionRuntimeIPCRouter extends WebAssemblyExtensionRuntimeObject {
routes = new Map()
pointer = NULL
constructor (adapter) {
super(adapter)
this.pointer = adapter.createExternalReferenceValue(this)
}
map (route, callback) {
this.routes.set(route, callback)
}
unmap (route) {
this.routes.delete(route)
}
invoke (context, url, size, bytes, callback) {
if (typeof size === 'function') {
callback = size
bytes = null
size = 0
}
if (!url.startsWith('ipc://')) {
url = `ipc://${url}`
}
const { adapter } = this
const { name } = ipc.Message.from(url)
if (this.routes.has(name)) {
const routeContext = new WebAssemblyExtensionRuntimeContext(adapter, {
context
})
const message = new WebAssemblyExtensionRuntimeIPCMessage(
adapter,
routeContext,
url,
null,
bytes
)
routeContext.internal.callback = callback
const route = this.routes.get(name)
route(routeContext, message, this)
return true
}
return false
}
}
class WebAssemblyExtensionRuntimePolicy extends WebAssemblyExtensionRuntimeObject {
name = null
allowed = false
constructor (adapter, name, allowed) {
super(adapter)
this.name = name
this.allowed = Boolean(allowed)
}
}
class WebAssemblyExtensionRuntimeContext extends WebAssemblyExtensionRuntimeObject {
// internal pointers
data = NULL
pointer = NULL
pool = []
error = null
router = null
internal = { callback: null }
config = Object.create(application.config)
context = null
retained = false
policies = new Map()
retainedCount = 0
constructor (adapter, options) {
super(adapter)
this.context = options?.context ?? null
const policies = options?.policies ?? options?.context?.policies ?? null
if (Array.isArray(policies)) {
for (const policy of policies) {
this.setPolicy(policy, true)
}
}
this.router = this.context?.router ?? new WebAssemblyExtensionRuntimeIPCRouter(adapter)
this.config = this.context?.config ? Object.create(this.context.config) : this.config
this.retained = Boolean(options?.retained)
this.policies = this.context?.policies ?? this.policies
this.pointer = this.context
? this.context.alloc(WebAssemblyExtensionRuntimeObject.pointerSize)
: this.adapter.heap.alloc(WebAssemblyExtensionRuntimeObject.pointerSize)
this.adapter.setExternalReferenceValue(this.pointer, this)
}
alloc (size) {
const pointer = this.adapter.heap.alloc(size)
if (pointer) {
this.pool.push(pointer)
}
return pointer
}
free (pointer) {
const index = this.pool.indexOf(pointer)
if (index > -1) {
this.pool.splice(index, 1)
this.adapter.heap.free(pointer)
}
}
createExternalReferenceValue (value) {
const pointer = this.adapter.createExternalReferenceValue(value)
if (pointer) {
this.pool.push(pointer)
}
return pointer
}
retain () {
this.retained = true
this.retainedCount++
}
release () {
if (this.retainedCount === 0) {
return false
}
if (--this.retainedCount === 0) {
for (const pointer of this.pool) {
const object = this.adapter.getExternalReferenceValue(pointer)
if (typeof object?.release === 'function') {
object.release()
}
this.adapter.heap.free(pointer)
}
this.adapter.heap.free(this.pointer)
this.pool.splice(0, this.pool.length)
this.pointer = NULL
this.retained = false
this.context = null
return true
}
return false
}
isAllowed (name) {
const names = name.split(',')
for (const name of names) {
const parts = name.trim().split('_')
let key = ''
for (const part of parts) {
key += part
if (this.hasPolicy(key) && this.getPolicy(key).allowed) {
return true
}
key += '_'
}
}
return false
}
setPolicy (name, allowed) {
if (this.hasPolicy(name)) {
const policy = this.getPolicy(name)
policy.name = name
policy.allowed = Boolean(allowed)
} else {
this.policies.set(name, new WebAssemblyExtensionRuntimePolicy(
this.adapter,
name,
allowed
))
}
}
getPolicy (name) {
return this.policies.get(name) ?? null
}
hasPolicy (name) {
return this.policies.has(name)
}
}
/**
* A constructed `Response` suitable for WASM binaries.
* @ignore
*/
class WebAssemblyResponse extends Response {
constructor (stream) {
super(stream, {
headers: {
'content-type': 'application/wasm'
}
})
}
}
class WebAssemblyExtensionExternalReference {
adapter = null
pointer = NULL
value = null
constructor (adapter, value, pointer) {
// box primitive values for proper equality comparisons
if (typeof value === 'string') {
// eslint-disable-next-line
value = new String(value)
} else if (typeof value === 'number') {
// eslint-disable-next-line
value = new Number(value)
} else if (typeof value === 'boolean') {
// eslint-disable-next-line
value = new Boolean(value)
}
this.adapter = adapter
this.pointer = pointer || adapter.heap.alloc(4)
this.value = value
}
valueOf () {
return this.value?.valueOf?.() ?? undefined
}
toString () {
return this.value?.toString?.() ?? ''
}
}
class WebAssemblyExtensionMemoryBlock {
memory = null
start = 0
end = 0
constructor (memory, start = null, end = null) {
this.memory = memory
this.start = start || 0
this.end = end || memory.byteLength
}
}
class WebAssemblyExtensionMemory {
adapter = null
offset = 0
current = []
limits = { min: 0, max: 0 }
freeList = []
constructor (adapter, min, max) {
this.adapter = adapter
this.limits.min = min
this.limits.max = max
this.offset = min
this.freeList.push(new WebAssemblyExtensionMemoryBlock(this))
}
get buffer () {
return this.adapter.buffer
}
get byteLength () {
return this.buffer.byteLength
}
get pointer () {
return this.offset
}
get (pointer, size = -1) {
if (size > -1) {
return this.buffer.subarray(pointer, pointer + size)
} else {
return this.buffer.subarray(pointer)
}
}
set (pointer, byteOrBytes, size) {
if (!pointer || !size) {
return false
}
if (typeof byteOrBytes === 'number') {
const byte = byteOrBytes
byteOrBytes = new Uint8Array(size)
byteOrBytes.fill(byte)
}
this.buffer.set(byteOrBytes.slice(0, size), pointer)
return true
}
push (value) {
let pointer = this.offset
if (value === null) {
value = 0
}
if (typeof value === 'string') {
value = this.adapter.textEncoder.encode(value)
} else if (isBufferLike(value)) {
value = new Uint8Array(value)
}
if (!isBufferLike(value) && typeof value !== 'number') {
throw new TypeError(
'WebAssemblyExtensionMemory: ' +
'Expected value to be TypedArray or a number: ' +
`Got ${typeof value}`
)
}
let byteLength = 0
const isFloat = typeof value === 'number' && parseInt(String(value)) !== value
if (isBufferLike(value)) {
byteLength = value.byteLength
pointer = this.alloc(byteLength)
} else {
const zeroes = Math.clz32(value)
if (zeroes <= 8) {
byteLength = 4
} else if (zeroes <= 16) {
byteLength = 2
} else if (zeroes <= 32) {
byteLength = 1
}
}
this.current.push(byteLength)
this.offset += byteLength
if (isBufferLike(value)) {
this.adapter.set(pointer, value)
} else if (byteLength === 4) {
if (isFloat) {
this.adapter.setFloat32(pointer, value)
} else {
this.adapter.setInt32(pointer, value)
}
} else if (byteLength === 2) {
if (isFloat) {
this.adapter.setFloat16(pointer, value)
} else {
this.adapter.setInt16(pointer, value)
}
} else if (byteLength === 1) {
if (isFloat) {
this.adapter.setFloat8(pointer, value)
} else {
this.adapter.setInt8(pointer, value)
}
}
return pointer
}
pop () {
if (this.current.length === 0) {
return 0
}
const byteLength = this.current.pop()
const pointer = this.offset - byteLength
this.offset -= byteLength
return pointer
}
restore (offset) {
const pointers = []
while (this.offset > offset) {
pointers.push(this.pop())
}
return pointers
}
alloc (size) {
if (!size) {
return NULL
}
// find a free block that is large enough
for (let i = 0; i < this.freeList.length; ++i) {
const block = this.freeList[i]
if (block.end - block.start >= size) {
// allocate memory from the free block
const allocatedBlock = new WebAssemblyExtensionMemoryBlock(
this,
block.start,
block.start + size
)
// update the free block (if any remaining)
if (block.end - allocatedBlock.end > 0) {
block.start = allocatedBlock.end
} else {
// remove the block from the free list if no remaining space
this.freeList.splice(i, 1)
}
this.offset = allocatedBlock.end
const pointer = this.limits.min + allocatedBlock.start
return pointer
}
}
return NULL
}
free (pointer) {
if (!pointer) {
return
}
if (this.adapter.externalReferences.has(pointer)) {
this.adapter.externalReferences.delete(pointer)
}
pointer = pointer - this.limits.min
// find the block to free and merge adjacent free blocks
for (let i = 0; i < this.freeList.length; ++i) {
const block = this.freeList[i]
if (pointer >= block.start && pointer < block.end) {
// add the freed block back to the free list
this.freeList.splice(i, 0, new WebAssemblyExtensionMemoryBlock(
this,
pointer,
pointer + (block.end - block.start)
))
// merge adjacent free blocks
if (i < this.freeList.length - 1 && this.freeList[i + 1].start === block.end) {
this.freeList[i].end = this.freeList[i + 1].end
this.freeList.splice(i + 1, 1)
}
if (i > 0 && this.freeList[i - 1].end === block.start) {
this.freeList[i - 1].end = block.end
this.freeList.splice(i, 1)
}
this.offset = this.freeList[this.freeList.length - 1].start
return
}
}
}
}
class WebAssemblyExtensionStack extends WebAssemblyExtensionMemory {
constructor (adapter) {
super(
adapter,
adapter.instance.exports.__stack_low.value,
adapter.instance.exports.__stack_high.value
)
}
}
class WebAssemblyExtensionHeap extends WebAssemblyExtensionMemory {
constructor (adapter) {
super(
adapter,
adapter.instance.exports.__heap_base.value,
adapter.instance.exports.__heap_end.value
)
}
realloc (pointer, size) {
// if the pointer is NULL, it's equivalent to `alloc()`
if (pointer === NULL) {
return this.alloc(size)
}
// find the block corresponding to the given pointer
for (let i = 0; i < this.freeList.length; ++i) {
const block = this.freeList[i]
if (pointer >= block.start && pointer < block.end) {
const currentSize = block.end - block.start
// if the new size is smaller or equal, return the existing pointer
if (size <= currentSize) {
return pointer
}
// try to extend the current block if there is enough space
if (
i < this.freeList.length - 1 &&
this.freeList[i + 1].start - block.end >= size - currentSize
) {
block.end += size - currentSize
return pointer
}
// if extension is not possible, allocate a new block and copy the data
const newPointer = this.alloc(size)
const sourceArray = new Uint8Array(this.buffer.buffer, block.start, currentSize)
const destinationArray = new Uint8Array(this.buffer.buffer, newPointer, currentSize)
destinationArray.set(sourceArray)
// free the original block
this.free(pointer)
return newPointer
}
}
// return NULL if the pointer is not found (not allocated)
return NULL
}
calloc (count, size) {
const totalSize = count * size
const pointer = this.alloc(totalSize)
if (pointer === NULL) {
// allocation failed
return NULL
}
// initialize the allocated memory to zero
const array = new Uint8Array(this.buffer.buffer, pointer, totalSize)
array.fill(0)
return pointer
}
}
class WebAssemblyExtensionIndirectFunctionTable {
constructor (adapter, options = null) {
this.adapter = adapter
this.table = (
options?.table ??
adapter.instance?.exports?.__indirect_function_table ??
new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
)
}
call (index, ...args) {
if (this.table.length === 0 || index >= this.table.length) {
return null
}
const callable = this.table.get(index)
const values = []
const mark = this.adapter.stack.pointer
for (const arg of args) {
if (typeof arg === 'number') {
values.push(arg)
} else {
values.push(this.adapter.stack.push(arg))
}
}
const value = callable(...values)
this.adapter.stack.restore(mark)
return value
}
}
/**
* An adapter for reading and writing various values from a WebAssembly instance's
* memory buffer.
* @ignore
*/
class WebAssemblyExtensionAdapter {
view = null
heap = null
table = null
stack = null
buffer = null
module = null
memory = null
context = null
policies = []
externalReferences = new Map() // mapping pointer addresses to javascript objects
instance = null
exitStatus = null
textDecoder = new TextDecoder()
textEncoder = new TextEncoder()
errorMessagePointers = {}
indirectFunctionTable = null
constructor ({ instance, module, table, memory, policies }) {
this.view = new DataView(memory.buffer)
this.table = table
this.buffer = new Uint8Array(memory.buffer)
this.module = module
this.memory = memory
this.policies = policies || []
this.instance = instance
this.indirectFunctionTable = new WebAssemblyExtensionIndirectFunctionTable(this)
}
get globalBaseOffset () {
return this.instance.exports.__global_base.value
}
destroy () {
this.view = null
this.table = null
this.buffer = null
this.module = null
this.memory = null
this.instance = null
this.textDecoder = null
this.textEncoder = null
this.indirectFunctionTable = null
this.stack = null
this.heap = null
this.context = null
}
init () {
this.stack = new WebAssemblyExtensionStack(this)
this.heap = new WebAssemblyExtensionHeap(this)
this.instance.exports.__wasm_call_ctors()
const offset = this.instance.exports.__sapi_extension_initializer()
this.context = new WebAssemblyExtensionRuntimeContext(this, {
policies: this.policies || []
})
// preallocate error message pointertr
for (const errorCode in errorMessages) {
const errorMessage = errorMessages[errorCode]
const pointer = this.heap.alloc(errorMessage.length)
this.errorMessagePointers[errorCode] = pointer
this.setString(pointer, errorMessage)
}
return Boolean(this.indirectFunctionTable.call(offset, this.context.pointer))
}
get (pointer, size = -1) {
if (!pointer) {
return null
}
if (size > -1) {
return this.buffer.subarray(pointer, pointer + size)
}
return this.buffer.subarray(pointer)
}
set (pointer, value) {
if (pointer) {
if (typeof value === 'string') {
value = this.textEncoder.encode(value)
}
this.buffer.set(value, pointer)
}
}
createExternalReferenceValue (value) {
const pointer = this.heap.alloc(4)
this.setExternalReferenceValue(pointer, value)
return pointer
}
getExternalReferenceValue (pointer) {
const ref = this.externalReferences.get(pointer)
if (ref) {
if (ref.value === null) {
return NULL
}
if (typeof ref.value === 'object') {
return ref.value.valueOf()
}
return ref.value
}
}
setExternalReferenceValue (pointer, value) {
return this.externalReferences.set(
pointer,
new WebAssemblyExtensionExternalReference(this, value, pointer)
)
}
removeExternalReferenceValue (pointer) {
this.externalReferences.delete(pointer)
}
getExternalReferencePointer (value) {
for (const ref of this.externalReferences.values()) {
if (ref.value === value) {
return ref.pointer
}
}
return NULL
}
getFloat32 (pointer) {
return pointer ? this.view.getFloat32(pointer, true) : 0
}
setFloat32 (pointer, value) {
return pointer ? (this.view.setFloat32(pointer, value, true), true) : false
}
getFloat64 (pointer) {
return pointer ? this.view.getFloat64(pointer, true) : 0
}
setFloat64 (pointer, value) {
return pointer ? (this.view.setFloat64(pointer, value, true), true) : false
}
getInt8 (pointer) {
return pointer ? this.view.getInt8(pointer, true) : 0
}
setInt8 (pointer, value) {
return pointer ? (this.view.setInt8(pointer, value, true), true) : false
}
getInt16 (pointer) {
return pointer ? this.view.getInt16(pointer, true) : 0
}
setInt16 (pointer, value) {
return pointer ? (this.view.setInt16(pointer, value, true), true) : false
}
getInt32 (pointer) {
return pointer ? this.view.getInt32(pointer, true) : 0
}
setInt32 (pointer, value) {
return pointer ? (this.view.setInt32(pointer, value, true), true) : false
}
getUint8 (pointer) {
return pointer ? this.view.getUint8(pointer, true) : 0
}
setUint8 (pointer, value) {
return pointer ? (this.view.setUint8(pointer, value, true), true) : false
}
getUint16 (pointer) {
return pointer ? this.view.getUint16(pointer, true) : 0
}
setUint16 (pointer, value) {
return pointer ? (this.view.setUint16(pointer, value, true), true) : false
}
getUint32 (pointer) {
return pointer ? this.view.getUint32(pointer, true) : 0
}
setUint32 (pointer, value) {
return pointer ? (this.view.setUint32(pointer, value, true), true) : false
}
getString (pointer, buffer, size) {
if (!pointer) {
return null
}
if (typeof buffer === 'number') {
size = buffer
buffer = null
}
const start = buffer
? buffer.slice(pointer)
: this.buffer.subarray(pointer)
const end = size || start.indexOf(0) // NULL byte
if (end === -1) {
return this.textDecoder.decode(start)
}
return this.textDecoder.decode(start.slice(0, end))
}
setString (pointer, string, buffer = null) {
if (pointer) {
const encoded = this.textEncoder.encode(string)
;(buffer || this.buffer).set(encoded, pointer)
return true
}
return false
}
}
/**
* A container for a native WebAssembly extension info.
* @ignore
*/
class WebAssemblyExtensionInfo {
adapter = null
constructor (adapter) {
this.adapter = adapter
}
get instance () {
return this.adapter.instance
}
get abi () {
return this.adapter.instance.exports.__sapi_extension_abi()
}
get name () {
return this.adapter.getString(this.instance.exports.__sapi_extension_name())
}
get version () {
return this.adapter.getString(this.instance.exports.__sapi_extension_version())
}
get description () {
return this.adapter.getString(this.instance.exports.__sapi_extension_description())
}
}
function createWebAssemblyExtensionBinding (extension, adapter) {
return new Proxy(adapter, {
get (_, key) {
if (typeof adapter.instance.exports[key] === 'function') {
return adapter.instance.exports[key].bind(adapter.instance.exports)
}
if (!adapter.context.router.routes.has(key)) {
key = `${extension.name}.${String(key)}`
}
if (adapter.context.router.routes.has(key)) {
return (options, ...args) => {
return new Promise((resolve, reject) => {
let params = null
if (typeof options === 'object') {
params = String(new URLSearchParams(options))
} else if (typeof options === 'string') {
params = options
}
const rest = Array.from(args).concat([resolve])
const url = `ipc://${key}?${params}`
try {
adapter.context.router.invoke(
adapter.context,
url,
...rest
)
} catch (err) {
reject(err)
}
})
}
}
return null
}
})
}
function variadicFormattedestinationPointerringFromPointers (
env,
formatPointer,
variadicArguments = 0x0,
encoded = true
) {
const format = env.adapter.getString(formatPointer)
if (!variadicArguments) {
if (encoded) {
return env.adapter.textEncoder.encode(format)
}
return format
}
const view = new DataView(env.adapter.buffer.buffer, variadicArguments)
let index = 0
const regex = /%(|l|ll|j|t|z|\.\*)+(l|d|f|i|n|o|p|s|S|u|x|X|%)+/gi
const output = format.replace(regex, (x) => {
if (x === '%%') {
return '%'
}
switch (x.toLowerCase()) {
case '%.*lu': case '%.*llu':
case '%.*ls': case '%.*lls':
case '%.*u': case '%.*s': {
const size = view.getInt32((index++) * 4, true)
const pointer = view.getInt32((index++) * 4, true)
const string = (
env.adapter.getString(pointer) ||
env.adapter.getExternalReferenceValue(pointer)?.value
)
if (!string || typeof string !== 'string') {
return '(null)'
}
return string.slice(0, size)
}
case '%s': {
const pointer = view.getInt32((index++) * 4, true)
const string = (
env.adapter.getString(pointer) ||
env.adapter.getExternalReferenceValue(pointer)?.value
)
if (!string || typeof string !== 'string') {
return '(null)'
}
return string
}
case '%zu': case '%u':
case '%llu': case '%lu': {
return view.getUint32((index) * 4, true)
}
case '%lld': case '%ld':
case '%i': case '%x': case '%d': {
return view.getInt32((index++) * 4, true)
}
case '%lp': case '%p': {
return `0x${view.getUint32((index++) * 4, true).toString(16)}`
}
case '%f': case '%lf': case '%llf': {
return view.getFloat64((index++) * 8, true)
}
}
return x
})
if (encoded) {
return env.adapter.textEncoder.encode(output)
}
return output
}
function createWebAssemblyExtensionImports (env) {
const imports = {
env: {
memoryBase: 5 * 1024 * 1024,
tableBase: 0,
memory: env.memory,
table: env.table || new WebAssembly.Table({
initial: 0,
element: 'anyfunc'
})
}
}
let atExitCallbackPointer = 0x0
let srandSeed = 1
// internal pointers
let basenamePointer = 0
let dirnamePointer = 0
let errnoPointer = 0
const toBeFlushed = {
stdout: [],
stderr: []
}
// <assert.h>
Object.assign(imports.env, {
__assert_fail (
conditionStringPointer,
filenameStringPointer,
lineNumber,
functionStringPointer
) {
console.error(
'Assertion failed: (%s), function %s, file %s, line %d.',
env.adapter.getString(conditionStringPointer),
env.adapter.getString(functionStringPointer),
env.adapter.getString(filenameStringPointer),
lineNumber
)
imports.env.abort()
}
})
// <ctype.h>
Object.assign(imports.env, {
isalnum (char) {
return imports.env.isalpha(char) || imports.env.isdigit(char)
},
isalpha (char) {
return char === EOF || imports.env.islower(char) || imports.env.isupper(char)
},
isascii (char) {
return char >= 0 && char <= parseInt('0177', 8)
},
isblank (char) {
return /\s/.test(String.fromCharCode(char))
},
iscntrl (char) {
const codes = [
'000', '001', '002', '003', '004',
'005', '006', '007', '010', '011',
'012', '013', '014', '015', '016',
'017', '020', '021', '022', '023',
'024', '025', '026', '027', '030',
'031', '032', '033', '034', '035',
'036', '037', '177'
].map((c) => parseInt(c, 8))
return codes.includes(char)
},
isdigit (char) {
return !Number.isNaN(parseInt(char, 10))
},
isgraph (char) {
char = String.fromCharCode(char)
return char.length === 1 && char >= '!' && char <= '~' && char !== ' '
},
islower (char) {
char = String.fromCharCode(char)
return char.length === 1 && char === char.toLowerCase()
},
isprint (char) {
char = String.fromCharCode(char)
return char.length === 1 && char >= ' ' && char <= '~'
},
ispunct (char) {
char = String.fromCharCode(char)
return char.length === 1 && /[!@#$%^&*()_+{}[]:;<>,.?~\\\/-=]/.test(char)
},
isspace (char) {
char = String.fromCharCode(char)
return char.length === 1 && /\s/.test(char)
},
isupper (char) {
char = String.fromCharCode(char)
return char.length === 1 && char === char.toUpperCase()
},
isxdigit (char) {
char = String.fromCharCode(char)
return char.length === 1 && /[0-9a-fA-F]/.test(char)
},
toascii (char) {
char = String.fromCharCode(char)
return char.length === 1 ? char.charCodeAt(0) & 0x7F : -1
},
tolower (char) {
char = String.fromCharCode(char)
return char.length === 1 ? char.toLowerCase() : char
},
toupper (char) {
char = String.fromCharCode(char)
return char.length === 1 ? char.toUpperCase() : char
}
})
// <errno.h>
Object.assign(imports.env, {
__errno_location () {
if (!errnoPointer) {
errnoPointer = env.adapter.heap.alloc(4)
}
return errnoPointer
}
})
// <libgen.h>
Object.assign(imports.env, {
basename (pathStringPointer) {
if (!basenamePointer) {
basenamePointer = env.adapter.heap.alloc(PATH_MAX + 1)
}
env.adapter.heap.set(basenamePointer, 0, PATH_MAX)
if (!pathStringPointer) {
env.adapter.setString(basenamePointer, '.')
} else {
const string = env.adapter.getString(pathStringPointer)
if (string) {
if (string === '/') {
env.adapter.setString(basenamePointer, '/')
} else if (string === '.') {
env.adapter.setString(basenamePointer, '.')
} else {
const basename = path.basename(string)
if (basename.length > PATH_MAX) {
env.adapter.setUint8(imports.env.__errno_location(), ENAMETOOLONG)
return NULL
}
env.adapter.setString(basenamePointer, basename)
}
} else {
env.adapter.setString(basenamePointer, '.')
}
}
return basenamePointer
},
dirname (pathStringPointer) {
if (!dirnamePointer) {
dirnamePointer = env.adapter.heap.alloc(PATH_MAX + 1)
}
env.adapter.heap.set(dirnamePointer, 0, PATH_MAX)
if (!pathStringPointer) {
env.adapter.setString(dirnamePointer, '.')
} else {
const string = env.adapter.getString(pathStringPointer)
if (string) {
if (string === '/') {
env.adapter.setString(dirnamePointer, '/')
} else if (string === '.') {
env.adapter.setString(dirnamePointer, '.')
} else {
const dirname = path.dirname(string)
if (dirname.length > PATH_MAX) {
env.adapter.setUint8(imports.env.__errno_location(), ENAMETOOLONG)
return NULL
}
env.adapter.setString(dirnamePointer, dirname)
}
} else {
env.adapter.setString(dirnamePointer, '.')
}
}
return dirnamePointer
}
})
// <math.h>
Object.assign(imports.env, {
__eqtf2 (x, y) { imports.env.abort() },
__trunctfsf2 (low, high) { imports.env.abort() },
__trunctfdf2 (low, high) { imports.env.abort() },
acos (value) { return Math.acos(value) },
acosl (value) { return Math.acos(value) },
acosf (value) { return Math.acos(value) },
agcosl (value) { return Math.acos(value) },
acosh (value) { return Math.acosh(value) },
acoshf (value) { return Math.acosh(value) },
acoshl (value) { return Math.acosh(value) },
asin (value) { return Math.asin(value) },
asinf (value) { return Math.asin(value) },
asinl (value) { return Math.asin(value) },
asinh (value) { return Math.asinh(value) },
asinhf (value) { return Math.asinh(value) },
asinhl (value) { return Math.asinh(value) },
atan (value) { return Math.atan(value) },
atanf (value) { return Math.atan(value) },
atanl (value) { return Math.atan(value) },
atanh (value) { return Math.atanh(value) },
atanhf (value) { return Math.atanh(value) },
atanhl (value) { return Math.atanh(value) },
atan2 (x, y) { return Math.atan2(x, y) },
atan2f (x, y) { return Math.atan2(x, y) },
atan2l (x, y) { return Math.atan2(x, y) },
cbrt (value) { return Math.cbrt(value) },
cbrtf (value) { return Math.cbrt(value) },
cbrtl (value) { return Math.cbrt(value) },
ceil (value) { return Math.ceil(value) },
ceilf (value) { return Math.ceil(value) },
ceill (value) { return Math.ceil(value) },
copysign (x, y) {
if (y === 0) {
return Math.abs(x)
}
if (x === 0) {
return 0
}
return Math.abs(x) * Math.sign(y)
},
copysignf (x, y) { return imports.env.copysign(x, y) },
copysignl (x, y) { return imports.env.copysign(x, y) },
cos (value) { return Math.cos(value) },
cosf (value) { return Math.cos(value) },
cosl (value) { return Math.cos(value) },
cosh (value) { return Math.cosh(value) },
coshf (value) { return Math.cosh(value) },
coshl (value) { return Math.cosh(value) },
erf (value) {
// constants for the rational approximations
const a1 = 0.254829592
const a2 = -0.284496736
const a3 = 1.421413741
const a4 = -1.453152027
const a5 = 1.061405429
// approximation formula
const x = value
const t = 1.0 / (1.0 + 0.3275911 * x)
const result = (a1 * t + a2 * t * t + a3 * t * t * t + a4 * t * t * t * t + a5 * t * t * t * t * t) * Math.exp(-x * x)
return x >= 0 ? 1.0 - result : result - 1.0
},
erff (value) { return imports.env.erf(value) },
erfl (value) { return imports.env.erf(value) },
erfcf (value) { return 1 - imports.env.erf(value) },
erfcl (value) { return 1 - imports.env.erf(value) },
exp (value) { return Math.exp(value) },
expf (value) { return Math.exp(value) },
expl (value) { return Math.exp(value) },
exp2 (value) {
const x = value * 1.4426950408889634 // approximation of log2(2)
const clamped = Math.max(-1022, Math.min(1023, x))
const bias = clamped + 1023
return new Float64Array(Float64Array.of(bias << 52).buffer)[0]
},
exp2f (value) { return imports.env.exp2(value) },
exp2l (value) { return imports.env.exp2(value) },
expm1 (value) { return Math.expm1(value) },
expm1f (value) { return Math.expm1(value) },
expm1l (value) { return Math.expm1(value) },
fabs (value) { return Math.abs(value) },
fabsf (value) { return Math.abs(value) },
fabsl (value) { return Math.abs(value) },
fdim (x, y) { return x > y ? x - y : 0.0 },
fdimf (x, y) { return x > y ? x - y : 0.0 },
fdiml (x, y) { return x > y ? x - y : 0.0 },
floor (value) { return Math.floor(value) },
floorf (value) { return Math.floor(value) },
floorl (value) { return Math.floor(value) },
fma (x, y, z) { return x * y + z },
fmaf (x, y, z) { return x * y + z },
fmal (x, y, z) { return x * y + z },
fmax (x, y) { return Math.max(x, y) },
fmaxf (x, y) { return Math.max(x, y) },
fmaxl (x, y) { return Math.max(x, y) },
fmin (x, y) { return Math.min(x, y) },
fminf (x, y) { return Math.min(x, y) },
fminl (x, y) { return Math.min(x, y) },