deno_ffi 0.37.0

Dynamic library ffi for deno
Documentation
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
"use strict";

((window) => {
  const core = window.Deno.core;
  const __bootstrap = window.__bootstrap;
  const {
    ArrayBufferPrototype,
    Uint8Array,
    BigInt,
    Number,
    ObjectDefineProperty,
    ObjectPrototypeIsPrototypeOf,
    TypeError,
  } = window.__bootstrap.primordials;

  function unpackU64([hi, lo]) {
    return BigInt(hi) << 32n | BigInt(lo);
  }

  function packU64(value) {
    return [Number(value >> 32n), Number(value & 0xFFFFFFFFn)];
  }

  function unpackI64([hi, lo]) {
    const u64 = unpackU64([hi, lo]);
    return u64 >> 63n ? u64 - 0x10000000000000000n : u64;
  }

  class UnsafePointerView {
    pointer;

    constructor(pointer) {
      this.pointer = pointer;
    }

    getUint8(offset = 0) {
      return core.opSync(
        "op_ffi_read_u8",
        packU64(this.pointer.value + BigInt(offset)),
      );
    }

    getInt8(offset = 0) {
      return core.opSync(
        "op_ffi_read_i8",
        packU64(this.pointer.value + BigInt(offset)),
      );
    }

    getUint16(offset = 0) {
      return core.opSync(
        "op_ffi_read_u16",
        packU64(this.pointer.value + BigInt(offset)),
      );
    }

    getInt16(offset = 0) {
      return core.opSync(
        "op_ffi_read_i16",
        packU64(this.pointer.value + BigInt(offset)),
      );
    }

    getUint32(offset = 0) {
      return core.opSync(
        "op_ffi_read_u32",
        packU64(this.pointer.value + BigInt(offset)),
      );
    }

    getInt32(offset = 0) {
      return core.opSync(
        "op_ffi_read_i32",
        packU64(this.pointer.value + BigInt(offset)),
      );
    }

    getBigUint64(offset = 0) {
      return unpackU64(core.opSync(
        "op_ffi_read_u64",
        packU64(this.pointer.value + BigInt(offset)),
      ));
    }

    getBigInt64(offset = 0) {
      return unpackI64(core.opSync(
        "op_ffi_read_u64",
        packU64(this.pointer.value + BigInt(offset)),
      ));
    }

    getFloat32(offset = 0) {
      return core.opSync(
        "op_ffi_read_f32",
        packU64(this.pointer.value + BigInt(offset)),
      );
    }

    getFloat64(offset = 0) {
      return core.opSync(
        "op_ffi_read_f64",
        packU64(this.pointer.value + BigInt(offset)),
      );
    }

    getCString(offset = 0) {
      return core.opSync(
        "op_ffi_cstr_read",
        packU64(this.pointer.value + BigInt(offset)),
      );
    }

    getArrayBuffer(byteLength, offset = 0) {
      const uint8array = new Uint8Array(byteLength);
      this.copyInto(uint8array, offset);
      return uint8array.buffer;
    }

    copyInto(destination, offset = 0) {
      core.opSync("op_ffi_buf_copy_into", [
        packU64(this.pointer.value + BigInt(offset)),
        destination,
        destination.byteLength,
      ]);
    }
  }

  class UnsafePointer {
    value;

    constructor(value) {
      this.value = value;
    }

    static of(typedArray) {
      return new UnsafePointer(
        unpackU64(core.opSync("op_ffi_ptr_of", typedArray)),
      );
    }

    valueOf() {
      return this.value;
    }
  }
  const UnsafePointerPrototype = UnsafePointer.prototype;

  function prepareArgs(types, args) {
    const parameters = [];
    const buffers = [];

    for (let i = 0; i < types.length; i++) {
      const type = types[i];
      const arg = args[i];

      if (type === "pointer") {
        if (
          ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, arg?.buffer) &&
          arg.byteLength !== undefined
        ) {
          parameters.push(buffers.length);
          buffers.push(arg);
        } else if (ObjectPrototypeIsPrototypeOf(UnsafePointerPrototype, arg)) {
          parameters.push(packU64(arg.value));
          buffers.push(undefined);
        } else if (arg === null) {
          parameters.push(null);
          buffers.push(undefined);
        } else {
          throw new TypeError(
            "Invalid ffi arg value, expected TypedArray, UnsafePointer or null",
          );
        }
      } else {
        parameters.push(arg);
      }
    }

    return { parameters, buffers };
  }

  class UnsafeFnPointer {
    pointer;
    definition;

    constructor(pointer, definition) {
      this.pointer = pointer;
      this.definition = definition;
    }

    call(...args) {
      const { parameters, buffers } = prepareArgs(
        this.definition.parameters,
        args,
      );
      if (this.definition.nonblocking) {
        const promise = core.opAsync("op_ffi_call_ptr_nonblocking", {
          pointer: packU64(this.pointer.value),
          def: this.definition,
          parameters,
          buffers,
        });

        if (this.definition.result === "pointer") {
          return promise.then((value) => new UnsafePointer(unpackU64(value)));
        }

        return promise;
      } else {
        const result = core.opSync("op_ffi_call_ptr", {
          pointer: packU64(this.pointer.value),
          def: this.definition,
          parameters,
          buffers,
        });

        if (this.definition.result === "pointer") {
          return new UnsafePointer(unpackU64(result));
        }

        return result;
      }
    }
  }

  class DynamicLibrary {
    #rid;
    symbols = {};

    constructor(path, symbols) {
      this.#rid = core.opSync("op_ffi_load", { path, symbols });

      for (const symbol in symbols) {
        if ("type" in symbols[symbol]) {
          const type = symbols[symbol].type;
          if (type === "void") {
            throw new TypeError(
              "Foreign symbol of type 'void' is not supported.",
            );
          }

          const name = symbols[symbol].name || symbol;
          let value = core.opSync(
            "op_ffi_get_static",
            {
              rid: this.#rid,
              name,
              type,
            },
          );
          if (type === "pointer" || type === "u64") {
            value = unpackU64(value);
            if (type === "pointer") {
              value = new UnsafePointer(value);
            }
          } else if (type === "i64") {
            value = unpackI64(value);
          }
          ObjectDefineProperty(
            this.symbols,
            symbol,
            {
              configurable: false,
              enumerable: true,
              value,
              writable: false,
            },
          );
          continue;
        }
        const isNonBlocking = symbols[symbol].nonblocking;
        const types = symbols[symbol].parameters;

        const fn = (...args) => {
          const { parameters, buffers } = prepareArgs(types, args);

          if (isNonBlocking) {
            const promise = core.opAsync("op_ffi_call_nonblocking", {
              rid: this.#rid,
              symbol,
              parameters,
              buffers,
            });

            if (symbols[symbol].result === "pointer") {
              return promise.then((value) =>
                new UnsafePointer(unpackU64(value))
              );
            }

            return promise;
          } else {
            const result = core.opSync("op_ffi_call", {
              rid: this.#rid,
              symbol,
              parameters,
              buffers,
            });

            if (symbols[symbol].result === "pointer") {
              return new UnsafePointer(unpackU64(result));
            }

            return result;
          }
        };

        ObjectDefineProperty(
          this.symbols,
          symbol,
          {
            configurable: false,
            enumerable: true,
            value: fn,
            writable: false,
          },
        );
      }
    }

    close() {
      core.close(this.#rid);
    }
  }

  function dlopen(path, symbols) {
    // URL support is progressively enhanced by util in `runtime/js`.
    const pathFromURL = __bootstrap.util.pathFromURL ?? ((p) => p);
    return new DynamicLibrary(pathFromURL(path), symbols);
  }

  window.__bootstrap.ffi = {
    dlopen,
    UnsafePointer,
    UnsafePointerView,
    UnsafeFnPointer,
  };
})(this);