diff --git a/configure.py b/configure.py index 7b82e0c5e86e7d..eea76312119385 100755 --- a/configure.py +++ b/configure.py @@ -2310,17 +2310,20 @@ def without_sqlite_error(option): configure_library('sqlite', o, pkgname='sqlite3') def bundled_ffi_supported(os_name, target_arch): - supported = { - 'freebsd': {'arm', 'arm64', 'x64'}, - 'linux': {'arm', 'arm64', 'x64'}, - 'mac': {'arm64', 'x64'}, - 'win': {'arm64', 'x64'}, - } - if target_arch == 'x86': target_arch = 'ia32' - return target_arch in supported.get(os_name, set()) + if target_arch in {'arm', 'arm64', 'ia32', 'x64', 'x86_64', + 'riscv64', 'loong64'}: + return True + + if target_arch in {'mips', 'mipsel', 'mips64el'}: + return os_name in {'freebsd', 'linux', 'openbsd'} + + if target_arch == 'ppc64': + return os_name in {'aix', 'freebsd', 'linux', 'mac', 'openbsd'} + + return False def configure_ffi(o): use_ffi = not options.without_ffi diff --git a/deps/libffi/generate-headers.py b/deps/libffi/generate-headers.py index 30a460bb27869c..d1b024450caba1 100644 --- a/deps/libffi/generate-headers.py +++ b/deps/libffi/generate-headers.py @@ -10,33 +10,63 @@ LIBFFI_VERSION = '3.5.2' LIBFFI_VERSION_NUMBER = '30502' -TARGETS = { - ('freebsd', 'arm'): ('ARM', 'arm'), - ('freebsd', 'arm64'): ('AARCH64', 'aarch64'), - ('freebsd', 'x64'): ('X86_64', 'x86'), - ('linux', 'arm'): ('ARM', 'arm'), - ('linux', 'arm64'): ('AARCH64', 'aarch64'), - ('linux', 'x64'): ('X86_64', 'x86'), - ('mac', 'arm64'): ('AARCH64', 'aarch64'), - ('mac', 'x64'): ('X86_64', 'x86'), - ('win', 'arm'): ('ARM_WIN32', 'arm'), - ('win', 'arm64'): ('ARM_WIN64', 'aarch64'), - ('win', 'x64'): ('X86_WIN64', 'x86'), -} +def normalize_arch(target_arch): + aliases = { + 'x86': 'ia32', + 'x86_64': 'x64', + 'arm64': 'arm64', + 'aarch64': 'arm64', + } + return aliases.get(target_arch, target_arch) def get_target(os_name, target_arch): - try: - return TARGETS[(os_name, target_arch)] - except KeyError as exc: - supported = ', '.join( - f'{os_name}/{arch}' for os_name, arch in sorted(TARGETS)) - raise ValueError( - f'Unsupported libffi target {os_name}/{target_arch}. ' - f'Supported targets: {supported}.') from exc + target_arch = normalize_arch(target_arch) + + if target_arch == 'arm': + return ('ARM_WIN32' if os_name == 'win' else 'ARM', 'arm') + + if target_arch == 'arm64': + return ('ARM_WIN64' if os_name == 'win' else 'AARCH64', 'aarch64') + + if target_arch == 'ia32': + if os_name in ('freebsd', 'openbsd'): + return ('X86_FREEBSD', 'x86') + if os_name in ('ios', 'mac'): + return ('X86_DARWIN', 'x86') + if os_name == 'win': + return ('X86_WIN32', 'x86') + return ('X86', 'x86') + + if target_arch == 'x64': + return ('X86_WIN64' if os_name == 'win' else 'X86_64', 'x86') + + if target_arch == 'riscv64': + return ('RISCV', 'riscv') + + if target_arch == 'loong64': + return ('LOONGARCH64', 'loongarch64') + + if target_arch in ('mips', 'mipsel', 'mips64el'): + if os_name in ('freebsd', 'linux', 'openbsd'): + return ('MIPS', 'mips') + + if target_arch == 'ppc64': + if os_name == 'aix': + return ('POWERPC_AIX', 'powerpc') + if os_name == 'mac': + return ('POWERPC_DARWIN', 'powerpc') + if os_name in ('freebsd', 'openbsd'): + return ('POWERPC_FREEBSD', 'powerpc') + if os_name == 'linux': + return ('POWERPC', 'powerpc') + + raise ValueError(f'Unsupported libffi target {os_name}/{target_arch}.') def has_long_double(os_name, target_arch): + target_arch = normalize_arch(target_arch) + if os_name == 'win': return '0' @@ -50,6 +80,7 @@ def has_long_double(os_name, target_arch): def uses_exec_trampoline_table(os_name, target_arch): + target_arch = normalize_arch(target_arch) return os_name == 'mac' and target_arch in ('arm', 'arm64') @@ -72,6 +103,7 @@ def render_ffi_header(base_dir, os_name, target_arch, target): def render_fficonfig(os_name, target_arch): + target_arch = normalize_arch(target_arch) lines = [ '/* Auto-generated by generate-headers.py. */', '#ifndef LIBFFI_FFICONFIG_H_', @@ -94,15 +126,15 @@ def render_fficonfig(os_name, target_arch): '#define EH_FRAME_FLAGS "a"', ]) - if target_arch in ('x64', 'x86') and os_name != 'win': + if target_arch in ('ia32', 'x64') and os_name != 'win': lines.append('#define HAVE_AS_X86_PCREL 1') if uses_exec_trampoline_table(os_name, target_arch): lines.append('#define FFI_EXEC_TRAMPOLINE_TABLE 1') - elif os_name in ('freebsd', 'mac'): + elif os_name in ('freebsd', 'mac', 'openbsd'): lines.append('#define FFI_MMAP_EXEC_WRIT 1') - if os_name == 'linux': + if os_name != 'win': lines.extend([ '#define HAVE_DLFCN_H 1', '#define HAVE_MMAP 1', @@ -111,15 +143,10 @@ def render_fficonfig(os_name, target_arch): '#define HAVE_SYS_MMAN_H 1', '#define HAVE_UNISTD_H 1', ]) - elif os_name in ('freebsd', 'mac'): + + if os_name in ('freebsd', 'ios', 'mac', 'openbsd'): lines.extend([ - '#define HAVE_DLFCN_H 1', - '#define HAVE_MMAP 1', - '#define HAVE_MMAP_ANON 1', '#define HAVE_MMAP_DEV_ZERO 1', - '#define HAVE_MMAP_FILE 1', - '#define HAVE_SYS_MMAN_H 1', - '#define HAVE_UNISTD_H 1', ]) if os_name == 'mac' and target_arch == 'arm64': @@ -204,12 +231,19 @@ def detect_target_arch(): 'x86_64': 'x64', 'x64': 'x64', 'win32': 'x64', - 'i386': 'x86', - 'i686': 'x86', - 'x86': 'x86', + 'i386': 'ia32', + 'i686': 'ia32', + 'ia32': 'ia32', + 'x86': 'ia32', 'arm64': 'arm64', 'aarch64': 'arm64', 'arm': 'arm', + 'riscv64': 'riscv64', + 'loong64': 'loong64', + 'ppc64': 'ppc64', + 'mips': 'mips', + 'mipsel': 'mipsel', + 'mips64el': 'mips64el', } for candidate in candidates: diff --git a/deps/libffi/libffi.gyp b/deps/libffi/libffi.gyp index cc83695f45b6ab..e02e959c19a001 100644 --- a/deps/libffi/libffi.gyp +++ b/deps/libffi/libffi.gyp @@ -15,7 +15,14 @@ 'conditions': [ ['OS == "win"', { 'conditions': [ - ['target_arch == "x64"', { + ['target_arch == "ia32" or target_arch == "x86"', { + 'variables': { + 'libffi_arch_sources': [ + 'src/x86/ffi.c', + ], + }, + }], + ['target_arch == "x64" or target_arch == "x86_64"', { 'variables': { 'libffi_arch_sources': [ 'src/x86/ffiw64.c', @@ -26,7 +33,6 @@ 'variables': { 'libffi_arch_sources': [ 'src/arm/ffi.c', - 'src/arm/sysv_msvc_arm32.S', ], }, }], @@ -39,9 +45,17 @@ }], ], }], - ['OS == "linux" or OS == "freebsd"', { + ['OS != "win"', { 'conditions': [ - ['target_arch == "x64"', { + ['target_arch == "ia32" or target_arch == "x86"', { + 'variables': { + 'libffi_arch_sources': [ + 'src/x86/ffi.c', + 'src/x86/sysv.S', + ], + }, + }], + ['target_arch == "x64" or target_arch == "x86_64"', { 'variables': { 'libffi_arch_sources': [ 'src/x86/ffiw64.c', @@ -67,25 +81,69 @@ ], }, }], - ], - }], - ['OS == "mac"', { - 'conditions': [ - ['target_arch == "x64"', { + ['target_arch == "riscv64"', { 'variables': { 'libffi_arch_sources': [ - 'src/x86/ffiw64.c', - 'src/x86/ffi64.c', - 'src/x86/unix64.S', - 'src/x86/win64.S', + 'src/riscv/ffi.c', + 'src/riscv/sysv.S', ], }, }], - ['target_arch == "arm64"', { + ['target_arch == "loong64"', { 'variables': { 'libffi_arch_sources': [ - 'src/aarch64/ffi.c', - 'src/aarch64/sysv.S', + 'src/loongarch64/ffi.c', + 'src/loongarch64/sysv.S', + ], + }, + }], + ['target_arch == "mips" or target_arch == "mipsel" or target_arch == "mips64el"', { + 'variables': { + 'libffi_arch_sources': [ + 'src/mips/ffi.c', + 'src/mips/o32.S', + 'src/mips/n32.S', + ], + }, + }], + ['target_arch == "ppc64" and OS == "mac"', { + 'variables': { + 'libffi_arch_sources': [ + 'src/powerpc/ffi_darwin.c', + 'src/powerpc/darwin.S', + 'src/powerpc/darwin_closure.S', + ], + }, + }], + ['target_arch == "ppc64" and OS == "aix"', { + 'variables': { + 'libffi_arch_sources': [ + 'src/powerpc/ffi_darwin.c', + 'src/powerpc/aix.S', + 'src/powerpc/aix_closure.S', + ], + }, + }], + ['target_arch == "ppc64" and (OS == "freebsd" or OS == "openbsd")', { + 'variables': { + 'libffi_arch_sources': [ + 'src/powerpc/ffi.c', + 'src/powerpc/ffi_sysv.c', + 'src/powerpc/sysv.S', + 'src/powerpc/ppc_closure.S', + ], + }, + }], + ['target_arch == "ppc64" and OS == "linux"', { + 'variables': { + 'libffi_arch_sources': [ + 'src/powerpc/ffi.c', + 'src/powerpc/ffi_sysv.c', + 'src/powerpc/ffi_linux64.c', + 'src/powerpc/sysv.S', + 'src/powerpc/ppc_closure.S', + 'src/powerpc/linux64.S', + 'src/powerpc/linux64_closure.S', ], }, }], @@ -107,6 +165,7 @@ ], 'include_dirs': [ 'include', + 'src', '<(SHARED_INTERMEDIATE_DIR)/libffi', ], 'sources': [ @@ -121,6 +180,10 @@ 'include/ffi.h.in', 'src/aarch64/ffitarget.h', 'src/arm/ffitarget.h', + 'src/loongarch64/ffitarget.h', + 'src/mips/ffitarget.h', + 'src/powerpc/ffitarget.h', + 'src/riscv/ffitarget.h', 'src/x86/ffitarget.h', ], 'outputs': [ @@ -141,7 +204,7 @@ }, ], 'conditions': [ - ['OS == "win" and target_arch == "x64"', { + ['OS == "win" and (target_arch == "x64" or target_arch == "x86_64")', { 'actions': [ { 'action_name': 'preprocess_win64_intel_asm', @@ -176,6 +239,75 @@ }, ], }], + ['OS == "win" and (target_arch == "ia32" or target_arch == "x86")', { + 'actions': [ + { + 'action_name': 'preprocess_win32_intel_asm', + 'process_outputs_as_sources': 1, + 'inputs': [ + 'preprocess_asm.py', + 'include/ffi_cfi.h', + 'src/x86/asmnames.h', + 'src/x86/sysv_intel.S', + '<(SHARED_INTERMEDIATE_DIR)/libffi/ffi.h', + '<(SHARED_INTERMEDIATE_DIR)/libffi/fficonfig.h', + ], + 'outputs': [ + '<(INTERMEDIATE_DIR)/sysv_intel.asm', + ], + 'action': [ + '<(python)', + 'preprocess_asm.py', + '--input', + 'src/x86/sysv_intel.S', + '--output', + '<@(_outputs)', + '--include-dir', + 'include', + '--include-dir', + 'src/x86', + '--include-dir', + '<(SHARED_INTERMEDIATE_DIR)/libffi', + '--define', + 'FFI_STATIC_BUILD', + ], + }, + ], + }], + ['OS == "win" and target_arch == "arm"', { + 'actions': [ + { + 'action_name': 'preprocess_win32_arm_asm', + 'process_outputs_as_sources': 1, + 'inputs': [ + 'preprocess_asm.py', + 'include/ffi_cfi.h', + 'src/arm/sysv_msvc_arm32.S', + '<(SHARED_INTERMEDIATE_DIR)/libffi/ffi.h', + '<(SHARED_INTERMEDIATE_DIR)/libffi/fficonfig.h', + ], + 'outputs': [ + '<(INTERMEDIATE_DIR)/sysv_msvc_arm32.asm', + ], + 'action': [ + '<(python)', + 'preprocess_asm.py', + '--input', + 'src/arm/sysv_msvc_arm32.S', + '--output', + '<@(_outputs)', + '--include-dir', + 'include', + '--include-dir', + 'src/arm', + '--include-dir', + '<(SHARED_INTERMEDIATE_DIR)/libffi', + '--define', + 'FFI_STATIC_BUILD', + ], + }, + ], + }], ['OS == "win" and target_arch == "arm64"', { # Link the prebuilt object file directly 'libraries': [ diff --git a/doc/api/ffi.md b/doc/api/ffi.md index 382b95fa07c3e3..37ccf2a719bc1a 100644 --- a/doc/api/ffi.md +++ b/doc/api/ffi.md @@ -30,15 +30,17 @@ const ffi = require('node:ffi'); This module is only available under the `node:` scheme in builds with FFI support and is gated by the `--experimental-ffi` flag. -Bundled libffi support currently targets: +Building Node.js with `node:ffi` support is available via the bundled `libffi` on +platforms where `libffi` provides a compatible static backend, or via a +shared `libffi` using the `--shared-ffi` configure flag. +The unofficial GN build does not support `node:ffi`. -* macOS on `arm64` and `x64` -* Windows on `arm64` and `x64` -* FreeBSD on `arm`, `arm64`, and `x64` -* Linux on `arm`, `arm64`, and `x64` +The following targets are not supported by bundled libffi: -Other targets require building Node.js against a shared libffi with -`--shared-ffi`. The unofficial GN build does not support `node:ffi`. +* `s390x`. +* `mips`, `mipsel`, and `mips64el` on targets other than FreeBSD, Linux, and + OpenBSD. +* `ppc64` on Android, CloudABI, iOS, OpenHarmony, OS/400, Solaris, and Windows. When using the [Permission Model][], FFI APIs are restricted unless the [`--allow-ffi`][] flag is provided. diff --git a/src/ffi/types.cc b/src/ffi/types.cc index 4d5062cdf832fb..e8469ebc0bbcc6 100644 --- a/src/ffi/types.cc +++ b/src/ffi/types.cc @@ -667,7 +667,7 @@ bool ToFFIReturnValue(Local result, ffi_type* type, void* ret) { return false; } - *static_cast(ret) = static_cast(value); + *static_cast(ret) = static_cast(value); } else if (type == &ffi_type_uint32) { uint64_t value; @@ -675,7 +675,7 @@ bool ToFFIReturnValue(Local result, ffi_type* type, void* ret) { return false; } - *static_cast(ret) = static_cast(value); + *static_cast(ret) = static_cast(value); } else if (type == &ffi_type_sint64) { bool lossless; diff --git a/test/ffi/ffi.status b/test/ffi/ffi.status index 8c917cfebadc59..a5274bfaf4af48 100644 --- a/test/ffi/ffi.status +++ b/test/ffi/ffi.status @@ -1,3 +1,9 @@ prefix ffi [true] # This section applies to all platforms + +[$system==solaris] # Also applies to SmartOS +# Bundled libffi callbacks crash on SmartOS. +test-ffi-calls: SKIP +test-ffi-shared-buffer: SKIP +test-ffi-weakref-calls: SKIP diff --git a/test/ffi/fixture_library/binding.gyp b/test/ffi/fixture_library/binding.gyp index ede9e9416da4f6..90b1ede016629f 100644 --- a/test/ffi/fixture_library/binding.gyp +++ b/test/ffi/fixture_library/binding.gyp @@ -4,6 +4,12 @@ 'target_name': 'ffi_test_library', 'sources': ['ffi_test_library.c'], 'type': 'shared_library', + 'conditions': [ + ['OS in "aix os400"', { + 'product_extension': 'so', + 'ldflags': [ '-Wl,-G' ], + }], + ], } ] } diff --git a/test/ffi/test-ffi-dynamic-library.js b/test/ffi/test-ffi-dynamic-library.js index ebe3af80586b45..e3171b57124250 100644 --- a/test/ffi/test-ffi-dynamic-library.js +++ b/test/ffi/test-ffi-dynamic-library.js @@ -4,6 +4,7 @@ const common = require('../common'); common.skipIfFFIMissing(); const { gcUntil } = require('../common/gc'); const assert = require('node:assert'); +const { endianness } = require('node:os'); const { test } = require('node:test'); const ffi = require('node:ffi'); const { fixtureSymbols, libraryPath } = require('./ffi-test-common'); @@ -54,8 +55,11 @@ test('dlopen resolves functions from definitions', () => { // Shared-buffer wrapper sets `length` to the FFI signature's arity // (see `inheritMetadata` in lib/internal/ffi-shared-buffer.js). The raw // native function has length 0, but the wrapper exposes the parameter - // count so `fn.length` is useful for introspection. - assert.strictEqual(functions.add_i32.length, 2); + // count so `fn.length` is useful for introspection. The shared-buffer + // wrapper is disabled on big-endian hosts. + assert.strictEqual( + functions.add_i32.length, + endianness() === 'BE' ? 0 : 2); assert.strictEqual(typeof functions.add_i32.pointer, 'bigint'); assert.strictEqual(Object.getPrototypeOf(functions), null); } finally { diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js index 5a6667fa512bcb..adc5a8539491d5 100644 --- a/test/ffi/test-ffi-memory.js +++ b/test/ffi/test-ffi-memory.js @@ -177,7 +177,7 @@ test('ffi exportString and exportBuffer copy data into native memory', () => { const viewSource = new Uint16Array([0x0102, 0x0304, 0x0506]); const middleBytes = new Uint8Array(viewSource.buffer, 2, 2); ffi.exportArrayBufferView(middleBytes, viewPtr, 2); - assert.deepStrictEqual([...ffi.toBuffer(viewPtr, 2)], [0x04, 0x03]); + assert.deepStrictEqual([...ffi.toBuffer(viewPtr, 2)], [...middleBytes]); const bufferViewPtr = alloc(8); const bufferView = Buffer.from([1, 7, 2, 8, 3]); diff --git a/test/ffi/test-ffi-shared-buffer.js b/test/ffi/test-ffi-shared-buffer.js index 43d76a4da18315..944b4021abc47a 100644 --- a/test/ffi/test-ffi-shared-buffer.js +++ b/test/ffi/test-ffi-shared-buffer.js @@ -4,8 +4,13 @@ const common = require('../common'); common.skipIfFFIMissing(); const assert = require('node:assert'); +const { endianness } = require('node:os'); const { test } = require('node:test'); +if (endianness() === 'BE') { + common.skip('shared-buffer FFI is disabled on big-endian hosts'); +} + // Capture the unpatched DynamicLibrary.prototype.getFunction BEFORE loading // `node:ffi`, which patches it. The SB-metadata test below uses the raw // method to inspect Symbol-keyed internals that `inheritMetadata`