From ec5a78d3dc01c9865c985ccfe2433b0f36745022 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 23 Mar 2026 09:03:08 -0500 Subject: [PATCH] Add pointer shift option for more nanboxing on arm64. We now try to detect this and do it by default. This is important as arm64 becomes more common and dominant. --- .github/workflows/test.yml | 2 +- meson.build | 3 +++ meson_options.txt | 1 + src/conf/janetconf.h | 1 + src/core/bytecode.c | 2 +- src/core/corelib.c | 2 +- src/core/inttypes.c | 26 +++++++++--------- src/core/util.c | 21 +++++++++++++++ src/core/wrap.c | 11 ++++++++ src/include/janet.h | 54 ++++++++++++++++++++++++++++---------- 10 files changed, 93 insertions(+), 30 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8e2be6ad..6b56aadb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, macos-14, macos-15-intel ] + os: [ ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-14, macos-15-intel ] steps: - name: Checkout the repository uses: actions/checkout@master diff --git a/meson.build b/meson.build index bc773371..2a30ca94 100644 --- a/meson.build +++ b/meson.build @@ -72,6 +72,9 @@ conf.set_quoted('JANET_VERSION', meson.project_version()) # Use options conf.set_quoted('JANET_BUILD', get_option('git_hash')) conf.set('JANET_NO_NANBOX', not get_option('nanbox')) +if get_option('nanbox_pointer_shift') != -1 # -1 is auto-detect + conf.set('JANET_NANBOX_64_POINTER_SHIFT', get_option('nanbox_pointer_shift')) +endif conf.set('JANET_SINGLE_THREADED', get_option('single_threaded')) conf.set('JANET_NO_DYNAMIC_MODULES', not get_option('dynamic_modules')) conf.set('JANET_NO_DOCSTRINGS', not get_option('docstrings')) diff --git a/meson_options.txt b/meson_options.txt index d055c713..3a833f4f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,6 +2,7 @@ option('git_hash', type : 'string', value : 'meson') option('single_threaded', type : 'boolean', value : false) option('nanbox', type : 'boolean', value : true) +option('nanbox_pointer_shift', type : 'integer', min : -1, max : 4, value : -1) option('dynamic_modules', type : 'boolean', value : true) option('docstrings', type : 'boolean', value : true) option('sourcemaps', type : 'boolean', value : true) diff --git a/src/conf/janetconf.h b/src/conf/janetconf.h index 92eaaa29..618a4c2c 100644 --- a/src/conf/janetconf.h +++ b/src/conf/janetconf.h @@ -16,6 +16,7 @@ /* #define JANET_THREAD_LOCAL _Thread_local */ /* #define JANET_NO_DYNAMIC_MODULES */ /* #define JANET_NO_NANBOX */ +/* #define JANET_NANBOX_64_POINTER_SHIFT 0 */ /* #define JANET_API __attribute__((visibility ("default"))) */ /* These settings should be specified before amalgamation is diff --git a/src/core/bytecode.c b/src/core/bytecode.c index 478f269a..6f7f2131 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -29,7 +29,7 @@ #endif /* Look up table for instructions */ -enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT] = { +const enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT] = { JINT_0, /* JOP_NOOP, */ JINT_S, /* JOP_ERROR, */ JINT_ST, /* JOP_TYPECHECK, */ diff --git a/src/core/corelib.c b/src/core/corelib.c index e420b736..8c39268a 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -70,7 +70,7 @@ JanetModule janet_native(const char *name, const uint8_t **error) { host.minor != modconf.minor || host.bits != modconf.bits) { char errbuf[128]; - snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)", + snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x) - native needs to be recompiled!", host.major, host.minor, host.patch, diff --git a/src/core/inttypes.c b/src/core/inttypes.c index fbf4fedd..ad659faf 100644 --- a/src/core/inttypes.c +++ b/src/core/inttypes.c @@ -333,7 +333,7 @@ static int compare_uint64_double(uint64_t x, double y) { } } -static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); if (janet_is_int(argv[0]) != JANET_INT_S64) { janet_panic("compare method requires int/s64 as first argument"); @@ -368,7 +368,7 @@ static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { return janet_wrap_nil(); } -static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); if (janet_is_int(argv[0]) != JANET_INT_U64) { janet_panic("compare method requires int/u64 as first argument"); @@ -416,7 +416,7 @@ static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { * This will not affect the end result (property of twos complement). */ #define OPMETHOD(T, type, name, oper) \ -static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_arity(argc, 2, -1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[0]); \ @@ -427,7 +427,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ } \ #define OPMETHODINVERT(T, type, name, oper) \ -static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 2); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[1]); \ @@ -437,7 +437,7 @@ static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ } \ #define UNARYMETHOD(T, type, name, oper) \ -static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = oper(janet_unwrap_##type(argv[0])); \ @@ -450,7 +450,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ #define DIVZERO_mod return janet_wrap_abstract(box) #define DIVMETHOD(T, type, name, oper) \ -static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_arity(argc, 2, -1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[0]); \ @@ -463,7 +463,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ } \ #define DIVMETHODINVERT(T, type, name, oper) \ -static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 2); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[1]); \ @@ -474,7 +474,7 @@ static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ } \ #define DIVMETHOD_SIGNED(T, type, name, oper) \ -static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_arity(argc, 2, -1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[0]); \ @@ -488,7 +488,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ } \ #define DIVMETHODINVERT_SIGNED(T, type, name, oper) \ -static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ +static JANET_CFUNCTION_ALIGN Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 2); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[1]); \ @@ -499,7 +499,7 @@ static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ return janet_wrap_abstract(box); \ } \ -static Janet cfun_it_s64_divf(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_divf(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); int64_t op1 = janet_unwrap_s64(argv[0]); @@ -510,7 +510,7 @@ static Janet cfun_it_s64_divf(int32_t argc, Janet *argv) { return janet_wrap_abstract(box); } -static Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); int64_t op2 = janet_unwrap_s64(argv[0]); @@ -521,7 +521,7 @@ static Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) { return janet_wrap_abstract(box); } -static Janet cfun_it_s64_mod(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_mod(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); int64_t op1 = janet_unwrap_s64(argv[0]); @@ -535,7 +535,7 @@ static Janet cfun_it_s64_mod(int32_t argc, Janet *argv) { return janet_wrap_abstract(box); } -static Janet cfun_it_s64_modi(int32_t argc, Janet *argv) { +static JANET_CFUNCTION_ALIGN Janet cfun_it_s64_modi(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); int64_t op2 = janet_unwrap_s64(argv[0]); diff --git a/src/core/util.c b/src/core/util.c index c3d3e2d7..66df0bf0 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -573,8 +573,24 @@ static char *namebuf_name(NameBuf *namebuf, const char *suffix) { return (char *)(namebuf->buf); } +/* Add a little bit of safety when using nanboxing on arm. Instead of inserting run-time checks everywhere, we are + * only doing it during registration which has much less cost (1 shift and mask). */ +static void janet_check_pointer_align(void *p) { + (void) p; +#if defined(JANET_NANBOX_64) && JANET_NANBOX_64_POINTER_SHIFT != 0 + union { + void *p; + uintptr_t u; + } un; + un.p = p; + janet_assert(!(un.u & (uintptr_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), + "unaligned pointer wrap - cfunction pointers and abstract types must be aligned with this nanboxing configuration."); +#endif +} + void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns) { while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); if (env) janet_def(env, cfuns->name, fun, cfuns->documentation); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, NULL, 0); @@ -584,6 +600,7 @@ void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns) void janet_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) { while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); if (env) janet_def_sm(env, cfuns->name, fun, cfuns->documentation, cfuns->source_file, cfuns->source_line); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line); @@ -595,6 +612,7 @@ void janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg * NameBuf nb; if (env) namebuf_init(&nb, regprefix); while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); if (env) janet_def(env, namebuf_name(&nb, cfuns->name), fun, cfuns->documentation); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, NULL, 0); @@ -607,6 +625,7 @@ void janet_cfuns_ext_prefix(JanetTable *env, const char *regprefix, const JanetR NameBuf nb; if (env) namebuf_init(&nb, regprefix); while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); if (env) janet_def_sm(env, namebuf_name(&nb, cfuns->name), fun, cfuns->documentation, cfuns->source_file, cfuns->source_line); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line); @@ -623,6 +642,7 @@ void janet_register(const char *name, JanetCFunction cfun) { /* Abstract type introspection */ void janet_register_abstract_type(const JanetAbstractType *at) { + janet_check_pointer_align((void *) at); Janet sym = janet_csymbolv(at->name); Janet check = janet_table_get(janet_vm.abstract_registry, sym); if (!janet_checktype(check, JANET_NIL) && at != janet_unwrap_pointer(check)) { @@ -655,6 +675,7 @@ void janet_core_def_sm(JanetTable *env, const char *name, Janet x, const void *p void janet_core_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) { (void) regprefix; while (cfuns->name) { + janet_check_pointer_align(cfuns->cfun); Janet fun = janet_wrap_cfunction(cfuns->cfun); janet_table_put(env, janet_csymbolv(cfuns->name), fun); janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line); diff --git a/src/core/wrap.c b/src/core/wrap.c index 2881326d..d44620e9 100644 --- a/src/core/wrap.c +++ b/src/core/wrap.c @@ -194,12 +194,18 @@ Janet janet_wrap_number_safe(double d) { void *janet_nanbox_to_pointer(Janet x) { x.i64 &= JANET_NANBOX_PAYLOADBITS; + x.u64 <<= JANET_NANBOX_64_POINTER_SHIFT; /* Alignment, usually 0 */ return x.pointer; } Janet janet_nanbox_from_pointer(void *p, uint64_t tagmask) { Janet ret; ret.pointer = p; + /* Should be noop when pointer shift is 0 */ + /* + janet_assert(!(ret.u64 & (uint64_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), "unaligned pointer wrap"); + */ + ret.u64 >>= JANET_NANBOX_64_POINTER_SHIFT; /* Alignment, usually 0 */ ret.u64 |= tagmask; return ret; } @@ -207,6 +213,11 @@ Janet janet_nanbox_from_pointer(void *p, uint64_t tagmask) { Janet janet_nanbox_from_cpointer(const void *p, uint64_t tagmask) { Janet ret; ret.pointer = (void *)p; + /* Should be noop when pointer shift is 0 */ + /* + janet_assert(!(ret.u64 & (uint64_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)), "unaligned pointer wrap"); + */ + ret.u64 >>= JANET_NANBOX_64_POINTER_SHIFT; /* Alignment, usually 0 */ ret.u64 |= tagmask; return ret; } diff --git a/src/include/janet.h b/src/include/janet.h index ac2e3019..eb356024 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -307,25 +307,38 @@ extern "C" { * architectures (Nanboxing only tested on x86 and x64), comment out * the JANET_NANBOX define.*/ -#if defined(_M_ARM64) || defined(_M_ARM) || defined(__aarch64__) -#define JANET_NO_NANBOX -#endif - #ifndef JANET_NO_NANBOX #ifdef JANET_32 #define JANET_NANBOX_32 -#elif defined(__x86_64__) || defined(_WIN64) || defined(__riscv) +#elif defined(__x86_64__) || defined(_WIN64) || defined(__riscv) || defined(__aarch64__) || defined(_M_ARM64) /* We will only enable nanboxing by default on 64 bit systems - * for x64 and risc-v. This is mainly because the approach is tied to the + * for x64, risc-v, and arm64. This is mainly because the approach is tied to the * implicit 47 bit address space. Many arches allow/require this, but not all, - * and it requires cooperation from the OS. ARM should also work in many configurations. */ + * and it requires cooperation from the OS. ARM should also work in many configurations by taking advantage + * of pointer alignment to allow for 48 or 49 bits of address space. */ #define JANET_NANBOX_64 + +/* Allow 64-bit nanboxing to assume aligned pointers to get back some extra bits for representation. + * This is needed to use nanboxing on systems with larger than 47-bit address spaces, such as many + * aarch64 systems. */ +#ifndef JANET_NANBOX_64_POINTER_SHIFT +#if (defined(_M_ARM64) || defined(__aarch64__)) && !defined(JANET_APPLE) +/* All pointers, including function pointers, should be 4-byte aligned on aarch64 by default. + * The exception is aarch64 macos, as it uses the same 47-bit userland address-space as on amd64. */ +#define JANET_NANBOX_64_POINTER_SHIFT 2 #endif #endif +#endif +#endif + +/* Allow for custom pointer alignment as well */ +#if defined(JANET_NANBOX_64) && !defined(JANET_NANBOX_64_POINTER_SHIFT) +#define JANET_NANBOX_64_POINTER_SHIFT 0 +#endif /* Runtime config constants */ #ifdef JANET_NO_NANBOX -#define JANET_NANBOX_BIT 0 +#define JANET_NANBOX_BIT 0x0 #else #define JANET_NANBOX_BIT 0x1 #endif @@ -336,9 +349,16 @@ extern "C" { #define JANET_SINGLE_THREADED_BIT 0 #endif +#ifdef JANET_NANBOX_64_POINTER_SHIFT +#define JANET_NANBOX_POINTER_SHIFT_BITS (JANET_NANBOX_64_POINTER_SHIFT ? (0x4 << JANET_NANBOX_64_POINTER_SHIFT) : 0) +#else +#define JANET_NANBOX_POINTER_SHIFT_BITS 0 +#endif + #define JANET_CURRENT_CONFIG_BITS \ (JANET_SINGLE_THREADED_BIT | \ - JANET_NANBOX_BIT) + JANET_NANBOX_BIT | \ + JANET_NANBOX_POINTER_SHIFT_BITS) /* Represents the settings used to compile Janet, as well as the version */ typedef struct { @@ -1415,7 +1435,7 @@ enum JanetOpCode { }; /* Info about all instructions */ -extern enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT]; +extern const enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT]; /***** END SECTION OPCODES *****/ @@ -2063,8 +2083,14 @@ JANET_API Janet janet_resolve_core(const char *name); * * */ +#if defined(JANET_NANBOX_64) && (JANET_NANBOX_64_POINTER_SHIFT != 0) && !defined(JANET_MSVC) +#define JANET_CFUNCTION_ALIGN __attribute__((aligned(1 << JANET_NANBOX_64_POINTER_SHIFT))) +#else +#define JANET_CFUNCTION_ALIGN +#endif + /* Shorthand for janet C function declarations */ -#define JANET_CFUN(name) Janet name (int32_t argc, Janet *argv) +#define JANET_CFUN(name) JANET_CFUNCTION_ALIGN Janet name (int32_t argc, Janet *argv) /* Declare a C function with documentation and source mapping */ #define JANET_REG_END {NULL, NULL, NULL, NULL, 0} @@ -2080,7 +2106,7 @@ JANET_API Janet janet_resolve_core(const char *name); #define JANET_REG_S(JNAME, CNAME) {JNAME, CNAME, NULL, __FILE__, CNAME##_sourceline_} #define JANET_FN_S(CNAME, USAGE, DOCSTRING) \ static const int32_t CNAME##_sourceline_ = __LINE__; \ - Janet CNAME (int32_t argc, Janet *argv) + Janet JANET_CFUNCTION_ALIGN CNAME (int32_t argc, Janet *argv) #define JANET_DEF_S(ENV, JNAME, VAL, DOC) \ janet_def_sm(ENV, JNAME, VAL, NULL, __FILE__, __LINE__) @@ -2088,7 +2114,7 @@ JANET_API Janet janet_resolve_core(const char *name); #define JANET_REG_D(JNAME, CNAME) {JNAME, CNAME, CNAME##_docstring_, NULL, 0} #define JANET_FN_D(CNAME, USAGE, DOCSTRING) \ static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \ - Janet CNAME (int32_t argc, Janet *argv) + Janet JANET_CFUNCTION_ALIGN CNAME (int32_t argc, Janet *argv) #define JANET_DEF_D(ENV, JNAME, VAL, DOC) \ janet_def(ENV, JNAME, VAL, DOC) @@ -2097,7 +2123,7 @@ JANET_API Janet janet_resolve_core(const char *name); #define JANET_FN_SD(CNAME, USAGE, DOCSTRING) \ static const int32_t CNAME##_sourceline_ = __LINE__; \ static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \ - Janet CNAME (int32_t argc, Janet *argv) + Janet JANET_CFUNCTION_ALIGN CNAME (int32_t argc, Janet *argv) #define JANET_DEF_SD(ENV, JNAME, VAL, DOC) \ janet_def_sm(ENV, JNAME, VAL, DOC, __FILE__, __LINE__)