From 517e40a17b8d42cfc1cae29f2d0b9015da84c9a9 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Mon, 2 Feb 2026 19:39:45 -0600 Subject: [PATCH] Speed up symbol resolution. In my testing of `janet -l spork -e '(os/exit)'`, this speeds up compilation by ~5%. Allocating and interning symbols from C is often slow, so avoid it. Add utility function for looking up keywords in tables more quickly. More measurement on compiling spork for compilation speed, win over original code closer to between 1-2% improvement --- src/core/compile.c | 2 +- src/core/os.c | 4 +-- src/core/specials.c | 16 ++++++------ src/core/table.c | 11 +++++++++ src/core/util.c | 60 ++++++++++++++++++++++++++++++++++++++++----- src/core/util.h | 30 +++++++++++++++++++++++ 6 files changed, 105 insertions(+), 18 deletions(-) diff --git a/src/core/compile.c b/src/core/compile.c index 45a252a7..2c68555a 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -295,7 +295,7 @@ JanetSlot janetc_resolve( { JanetBinding binding = janet_resolve_ext(c->env, sym); if (binding.type == JANET_BINDING_NONE) { - Janet handler = janet_table_get(c->env, janet_ckeywordv("missing-symbol")); + Janet handler = janet_table_get_keyword(c->env, "missing-symbol"); switch (janet_type(handler)) { case JANET_NIL: break; diff --git a/src/core/os.c b/src/core/os.c index 9149ac18..8e514113 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1959,7 +1959,7 @@ static int entry_getdst(Janet env_entry) { Janet v; if (janet_checktype(env_entry, JANET_TABLE)) { JanetTable *entry = janet_unwrap_table(env_entry); - v = janet_table_get(entry, janet_ckeywordv("dst")); + v = janet_table_get_keyword(entry, "dst"); } else if (janet_checktype(env_entry, JANET_STRUCT)) { const JanetKV *entry = janet_unwrap_struct(env_entry); v = janet_struct_get(entry, janet_ckeywordv("dst")); @@ -1983,7 +1983,7 @@ static timeint_t entry_getint(Janet env_entry, char *field) { Janet i; if (janet_checktype(env_entry, JANET_TABLE)) { JanetTable *entry = janet_unwrap_table(env_entry); - i = janet_table_get(entry, janet_ckeywordv(field)); + i = janet_table_get_keyword(entry, field); } else if (janet_checktype(env_entry, JANET_STRUCT)) { const JanetKV *entry = janet_unwrap_struct(env_entry); i = janet_struct_get(entry, janet_ckeywordv(field)); diff --git a/src/core/specials.c b/src/core/specials.c index ffa77821..7f4646a2 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -307,11 +307,11 @@ static JanetSlot janetc_varset(JanetFopts opts, int32_t argn, const Janet *argv) /* Add attributes to a global def or var table */ static JanetTable *handleattr(JanetCompiler *c, const char *kind, int32_t argn, const Janet *argv) { int32_t i; - JanetTable *tab = janet_table(2); if (argn < 2) { janetc_error(c, janet_formatc("expected at least 2 arguments to %s", kind)); return NULL; } + JanetTable *tab = janet_table(2); const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL ? ((const char *)janet_unwrap_symbol(argv[0])) : ""; @@ -443,8 +443,7 @@ static int varleaf( JanetSlot refslot; JanetTable *entry = janet_table_clone(reftab); - Janet redef_kw = janet_ckeywordv("redef"); - int is_redef = janet_truthy(janet_table_get(c->env, redef_kw)); + int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef")); JanetArray *ref; JanetBinding old_binding; @@ -464,7 +463,7 @@ static int varleaf( janetc_emit_ssu(c, JOP_PUT_INDEX, refslot, s, 0, 0); return 1; } else { - int no_unused = reftab && reftab->count && janet_truthy(janet_table_get(reftab, janet_ckeywordv("unused"))); + int no_unused = reftab && reftab->count && janet_truthy(janet_table_get_keyword(reftab, "unused")); return namelocal(c, sym, JANET_SLOT_MUTABLE, s, no_unused); } } @@ -472,7 +471,7 @@ static int varleaf( static void check_metadata_lint(JanetCompiler *c, JanetTable *attr_table) { if (!(c->scope->flags & JANET_SCOPE_TOP) && attr_table && attr_table->count) { /* A macro is a normal lint, other metadata is a strict lint */ - if (janet_truthy(janet_table_get(attr_table, janet_ckeywordv("macro")))) { + if (janet_truthy(janet_table_get_keyword(attr_table, "macro"))) { janetc_lintf(c, JANET_C_LINT_NORMAL, "macro tag is ignored in inner scopes"); } } @@ -511,9 +510,8 @@ static int defleaf( janet_table_put(entry, janet_ckeywordv("source-map"), janet_wrap_tuple(janetc_make_sourcemap(c))); - Janet redef_kw = janet_ckeywordv("redef"); - int is_redef = janet_truthy(janet_table_get(c->env, redef_kw)); - if (is_redef) janet_table_put(entry, redef_kw, janet_wrap_true()); + int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef")); + if (is_redef) janet_table_put(entry, janet_ckeywordv("redef"), janet_wrap_true()); if (is_redef) { JanetBinding binding = janet_resolve_ext(c->env, sym); @@ -536,7 +534,7 @@ static int defleaf( /* Add env entry to env */ janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(entry)); } - int no_unused = tab && tab->count && janet_truthy(janet_table_get(tab, janet_ckeywordv("unused"))); + int no_unused = tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "unused")); return namelocal(c, sym, 0, s, no_unused); } diff --git a/src/core/table.c b/src/core/table.c index ffaf7591..e25e699f 100644 --- a/src/core/table.c +++ b/src/core/table.c @@ -155,6 +155,17 @@ Janet janet_table_get(JanetTable *t, Janet key) { return janet_wrap_nil(); } +/* Used internally for compiler stuff */ +Janet janet_table_get_keyword(JanetTable *t, const char *keyword) { + int32_t keyword_len = strlen(keyword); + for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) { + JanetKV *bucket = (JanetKV *) janet_dict_find_keyword(t->data, t->capacity, (const uint8_t *) keyword, keyword_len); + if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) + return bucket->value; + } + return janet_wrap_nil(); +} + /* Get a value out of the table, and record which prototype it was from. */ Janet janet_table_get_ex(JanetTable *t, Janet key, JanetTable **which) { for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) { diff --git a/src/core/util.c b/src/core/util.c index b45df256..723b64f2 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -321,6 +321,54 @@ const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key) { return first_bucket; } +/* Helper to find a keyword, symbol, or string in a Janet struct or table without allocating + * memory or needing to find interned symbols */ +const JanetKV *janet_dict_find_keyword( + const JanetKV *buckets, int32_t cap, + const uint8_t *cstr, int32_t cstr_len) { + int32_t hash = janet_string_calchash(cstr, cstr_len); + int32_t index = janet_maphash(cap, hash); + int32_t i; + const JanetKV *first_bucket = NULL; + /* Higher half */ + for (i = index; i < cap; i++) { + const JanetKV *kv = buckets + i; + if (janet_checktype(kv->key, JANET_NIL)) { + if (janet_checktype(kv->value, JANET_NIL)) { + return kv; + } else if (NULL == first_bucket) { + first_bucket = kv; + } + } else if (janet_checktype(kv->key, JANET_KEYWORD)) { + /* Works for symbol and keyword, too */ + JanetString str = janet_unwrap_string(kv->key); + int32_t len = janet_string_length(str); + if (hash == janet_string_hash(str) && len == cstr_len && !memcmp(str, cstr, len)) { + return buckets + i; + } + } + } + /* Lower half */ + for (i = 0; i < index; i++) { + const JanetKV *kv = buckets + i; + if (janet_checktype(kv->key, JANET_NIL)) { + if (janet_checktype(kv->value, JANET_NIL)) { + return kv; + } else if (NULL == first_bucket) { + first_bucket = kv; + } + } else if (janet_checktype(kv->key, JANET_KEYWORD)) { + /* Works for symbol and keyword, too */ + JanetString str = janet_unwrap_string(kv->key); + int32_t len = janet_string_length(str); + if (hash == janet_string_hash(str) && len == cstr_len && !memcmp(str, cstr, len)) { + return buckets + i; + } + } + } + return first_bucket; +} + /* Get a value from a janet struct or table. */ Janet janet_dictionary_get(const JanetKV *data, int32_t cap, Janet key) { const JanetKV *kv = janet_dict_find(data, cap, key); @@ -628,8 +676,11 @@ JanetBinding janet_binding_from_entry(Janet entry) { return binding; entry_table = janet_unwrap_table(entry); - /* deprecation check */ - Janet deprecate = janet_table_get(entry_table, janet_ckeywordv("deprecated")); + Janet deprecate = janet_table_get_keyword(entry_table, "deprecated"); + int macro = janet_truthy(janet_table_get_keyword(entry_table, "macro")); + Janet value = janet_table_get_keyword(entry_table, "value"); + Janet ref = janet_table_get_keyword(entry_table, "ref"); + if (janet_checktype(deprecate, JANET_KEYWORD)) { JanetKeyword depkw = janet_unwrap_keyword(deprecate); if (!janet_cstrcmp(depkw, "relaxed")) { @@ -643,11 +694,8 @@ JanetBinding janet_binding_from_entry(Janet entry) { binding.deprecation = JANET_BINDING_DEP_NORMAL; } - int macro = janet_truthy(janet_table_get(entry_table, janet_ckeywordv("macro"))); - Janet value = janet_table_get(entry_table, janet_ckeywordv("value")); - Janet ref = janet_table_get(entry_table, janet_ckeywordv("ref")); int ref_is_valid = janet_checktype(ref, JANET_ARRAY); - int redef = ref_is_valid && janet_truthy(janet_table_get(entry_table, janet_ckeywordv("redef"))); + int redef = ref_is_valid && janet_truthy(janet_table_get_keyword(entry_table, "redef")); if (macro) { binding.value = redef ? ref : value; diff --git a/src/core/util.h b/src/core/util.h index 7b550f50..874ef7b0 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -66,42 +66,72 @@ /* Utils */ uint32_t janet_hash_mix(uint32_t input, uint32_t more); + #define janet_maphash(cap, hash) ((uint32_t)(hash) & (cap - 1)) + int janet_valid_utf8(const uint8_t *str, int32_t len); + int janet_is_symbol_char(uint8_t c); + extern const char janet_base64[65]; + int32_t janet_array_calchash(const Janet *array, int32_t len); + int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len); + int32_t janet_string_calchash(const uint8_t *str, int32_t len); + int32_t janet_tablen(int32_t n); + void safe_memcpy(void *dest, const void *src, size_t len); + void janet_buffer_push_types(JanetBuffer *buffer, int types); + const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key); + void janet_memempty(JanetKV *mem, int32_t count); + void *janet_memalloc_empty(int32_t count); + JanetTable *janet_get_core_table(const char *name); + void janet_def_addflags(JanetFuncDef *def); + void janet_buffer_dtostr(JanetBuffer *buffer, double x); + const char *janet_strerror(int e); + const void *janet_strbinsearch( const void *tab, size_t tabcount, size_t itemsize, const uint8_t *key); + void janet_buffer_format( JanetBuffer *b, const char *strfrmt, int32_t argstart, int32_t argc, Janet *argv); + Janet janet_next_impl(Janet ds, Janet key, int is_interpreter); + JanetBinding janet_binding_from_entry(Janet entry); + JanetByteView janet_text_substitution( Janet *subst, const uint8_t *bytes, uint32_t len, JanetArray *extra_args); +const JanetKV *janet_dict_find_keyword( + const JanetKV *buckets, + int32_t cap, + const uint8_t *cstr, + int32_t cstr_len); + +Janet janet_table_get_keyword(JanetTable *t, const char *keyword); + /* Registry functions */ void janet_registry_put( JanetCFunction key,