shithub: femtolisp

Download patch

ref: af68820de45fb5675495e70c38d2688138701936
parent: 0ddd26de786eb67b38bc7a6fa371e7d3a87725db
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Tue Dec 24 22:12:41 EST 2024

symtab: use QP tries instead (can help with completion later too)

--- /dev/null
+++ b/3rd/fn.c
@@ -1,0 +1,209 @@
+// fn.c: quintet bit popcount patricia tries, new version
+//
+// Written by Tony Finch <dot@dotat.at>
+// You may do anything with this. It has no warranty.
+// <http://creativecommons.org/publicdomain/zero/1.0/>
+
+#include "platform.h"
+#include "tbl.h"
+#include "fn.h"
+
+bool
+Tgetkv(Tbl *t, const char *key, size_t len, const char **pkey, void **pval)
+{
+	if(t == nil)
+		return false;
+	while(isbranch(t)){
+		__builtin_prefetch(t->ptr);
+		Tindex i = t->index;
+		Tbitmap b = twigbit(i, key, len);
+		if(!hastwig(i, b))
+			return false;
+		t = Tbranch_twigs(t) + twigoff(i, b);
+	}
+	if(strcmp(key, Tleaf_key(t)) != 0)
+		return false;
+	*pkey = Tleaf_key(t);
+	*pval = Tleaf_val(t);
+	return true;
+}
+
+static bool
+next_rec(Trie *t, const char **pkey, size_t *plen, void **pval)
+{
+	Tindex i = t->index;
+	if(Tindex_branch(i)){
+		// Recurse to find either this leaf (*pkey != nil)
+		// or the next one (*pkey == nil).
+		Tbitmap b = twigbit(i, *pkey, *plen);
+		uint s, m; TWIGOFFMAX(s, m, i, b);
+		for(; s < m; s++){
+			if(next_rec(Tbranch_twigs(t)+s, pkey, plen, pval))
+				return true;
+		}
+		return false;
+	}
+	// We have found the next leaf.
+	if(*pkey == nil){
+		*pkey = Tleaf_key(t);
+		*plen = strlen(*pkey);
+		*pval = Tleaf_val(t);
+		return true;
+	}
+	// We have found this leaf, so start looking for the next one.
+	if(strcmp(*pkey, Tleaf_key(t)) == 0){
+		*pkey = nil;
+		*plen = 0;
+		return false;
+	}
+	// No match.
+	return false;
+}
+
+bool
+Tnextl(Tbl *tbl, const char **pkey, size_t *plen, void **pval)
+{
+	if(tbl == nil){
+		*pkey = nil;
+		*plen = 0;
+		return false;
+	}
+	return next_rec(tbl, pkey, plen, pval);
+}
+
+Tbl *
+Tdelkv(Tbl *tbl, const char *key, size_t len, const char **pkey, void **pval)
+{
+	if(tbl == nil)
+		return nil;
+	Trie *t = tbl, *p = nil;
+	Tindex i = 0;
+	Tbitmap b = 0;
+	while(isbranch(t)){
+		__builtin_prefetch(t->ptr);
+		i = t->index;
+		b = twigbit(i, key, len);
+		if(!hastwig(i, b))
+			return tbl;
+		p = t; t = Tbranch_twigs(t) + twigoff(i, b);
+	}
+	if(strcmp(key, Tleaf_key(t)) != 0)
+		return tbl;
+	*pkey = Tleaf_key(t);
+	*pval = Tleaf_val(t);
+	if(p == nil){
+		MEM_FREE(tbl);
+		return nil;
+	}
+	Trie *twigs = Tbranch_twigs(p);
+	uint m = popcount(Tindex_bitmap(i));
+	assert(twigs <= t && t < twigs+m);
+	if(m == 2){
+		// Move the other twig to the parent branch.
+		*p = twigs[twigs == t];
+		MEM_FREE(twigs);
+		return tbl;
+	}
+	memmove(t, t+1, ((twigs + m) - (t + 1)) * sizeof(Trie));
+	p->index = Tbitmap_del(i, b);
+	// We have now correctly removed the twig from the trie, so if
+	// MEM_REALLOC() fails we can ignore it and continue to use the
+	// slightly oversized twig array.
+	twigs = MEM_REALLOC(twigs, sizeof(Trie) * (m - 1));
+	if(twigs != nil)
+		Tset_twigs(p, twigs);
+	return tbl;
+}
+
+Tbl *
+Tsetl(Tbl *tbl, const char *key, size_t len, void *val)
+{
+	assert(!Tindex_branch((Tindex)val) && len <= Tmaxlen);
+	if(val == nil)
+		return Tdell(tbl, key, len);
+	// First leaf in an empty tbl?
+	if(tbl == nil){
+		tbl = MEM_ALLOC(sizeof(*tbl));
+		if(tbl == nil)
+			return nil;
+		Tset_key(tbl, key);
+		Tset_val(tbl, val);
+		return tbl;
+	}
+	Trie *t = tbl;
+	// Find the most similar leaf node in the trie. We will compare
+	// its key with our new key to find the first differing nibble,
+	// which can be at a lower index than the point at which we
+	// detect a difference.
+	while(isbranch(t)){
+		__builtin_prefetch(t->ptr);
+		Tindex i = t->index;
+		Tbitmap b = twigbit(i, key, len);
+		// Even if our key is missing from this branch we need to
+		// keep iterating down to a leaf. It doesn't matter which
+		// twig we choose since the keys are all the same up to this
+		// index. Note that blindly using twigoff(t, b) can cause
+		// an out-of-bounds index if it equals twigmax(t).
+		uint s = hastwig(i, b) ? twigoff(i, b) : 0;
+		t = Tbranch_twigs(t) + s;
+	}
+	// Do the keys differ, and if so, where?
+	uint off, xor, shf;
+	const char *tkey = Tleaf_key(t);
+	for(off = 0; off <= len; off++){
+		xor = (byte)key[off] ^ (byte)tkey[off];
+		if(xor != 0)
+			goto newkey;
+	}
+	Tset_val(t, val);
+	return tbl;
+newkey:; // We have the branch's byte index; what is its chunk index?
+	uint bit = off * 8 + __builtin_clz(xor) + 8 - sizeof(uint) * 8;
+	uint qo = bit / 5;
+	off = qo * 5 / 8;
+	shf = qo * 5 % 8;
+	// re-index keys with adjusted offset
+	Tbitmap nb = 1U << knybble(key,off,shf);
+	Tbitmap tb = 1U << knybble(tkey,off,shf);
+	// Prepare the new leaf.
+	Trie nt;
+	Tset_key(&nt, key);
+	Tset_val(&nt, val);
+	// Find where to insert a branch or grow an existing branch.
+	t = tbl;
+	Tindex i;
+	while(isbranch(t)){
+		__builtin_prefetch(t->ptr);
+		i = t->index;
+		if(off == Tindex_offset(i) && shf == Tindex_shift(i))
+			goto growbranch;
+		if(off == Tindex_offset(i) && shf < Tindex_shift(i))
+			goto newbranch;
+		if(off < Tindex_offset(i))
+			goto newbranch;
+		Tbitmap b = twigbit(i, key, len);
+		assert(hastwig(i, b));
+		t = Tbranch_twigs(t) + twigoff(i, b);
+	}
+newbranch:;
+	Trie *twigs = MEM_ALLOC(sizeof(Trie) * 2);
+	if(twigs == nil)
+		return nil;
+	i = Tindex_new(shf, off, nb | tb);
+	twigs[twigoff(i, nb)] = nt;
+	twigs[twigoff(i, tb)] = *t;
+	Tset_twigs(t, twigs);
+	Tset_index(t, i);
+	return tbl;
+growbranch:
+	assert(!hastwig(i, nb));
+	uint s, m; TWIGOFFMAX(s, m, i, nb);
+	twigs = MEM_REALLOC(Tbranch_twigs(t), sizeof(Trie) * (m + 1));
+	if(twigs == nil)
+		return nil;
+	memmove(twigs+s+1, twigs+s, sizeof(Trie) * (m - s));
+	memmove(twigs+s, &nt, sizeof(Trie));
+	Tset_twigs(t, twigs);
+	Tset_index(t, Tbitmap_add(i, nb));
+	return tbl;
+}
--- /dev/null
+++ b/3rd/fn.h
@@ -1,0 +1,220 @@
+// fn.h: quintet bit popcount patricia tries, new version
+//
+// This version uses somewhat different terminology than older
+// variants. The location of a quintet in the key is now called its
+// "offset", and the whole word containing the offset, bitmap, and tag
+// bit is called the "index word" (by analogy with a database index).
+// The precise quintet location is represented as a byte offset and a
+// shift. Previously a flags field contained the isbranch tag and shift,
+// but these are now separate.
+//
+// Instead of trying to use bit fields, this code uses accessor
+// functions to split up a pair of words into their constituent parts.
+// This should improve portability to machines with varying endianness
+// and/or word size.
+//
+// Written by Tony Finch <dot@dotat.at>
+// You may do anything with this. It has no warranty.
+// <http://creativecommons.org/publicdomain/zero/1.0/>
+
+typedef unsigned char byte;
+typedef unsigned int uint;
+
+typedef uint32_t Tbitmap;
+typedef uint64_t Tindex;
+
+const char *dump_bitmap(Tbitmap w);
+
+#if defined(__plan9__)
+static inline uint
+popcount(Tbitmap w)
+{
+	w -= (w >> 1) & 0x55555555;
+	w = (w & 0x33333333) + ((w >> 2) & 0x33333333);
+	w = (w + (w >> 4)) & 0x0F0F0F0F;
+	w = (w * 0x01010101) >> 24;
+	return(w);
+}
+#else
+#define popcount(w) __builtin_popcount(w)
+#endif
+
+typedef struct Tbl {
+	Tindex index;
+	void *ptr;
+}Trie;
+
+// accessor functions, except for the index word
+
+#define Tset_field(cast, elem, type, field) \
+	static inline void \
+	Tset_##field(Trie *t, type field) \
+	{ \
+		t->elem = cast field; \
+	} \
+	struct dummy
+
+Tset_field((void *),           ptr,   Trie *,       twigs);
+Tset_field((Tindex),           index, Tindex,       index);
+Tset_field((void *)(uint64_t), ptr,   const char *, key);
+Tset_field((Tindex),           index, void *,       val);
+
+static inline bool Tindex_branch(Tindex i);
+
+static inline bool isbranch(Trie *t)
+{
+	return(Tindex_branch(t->index));
+}
+
+#ifdef WITH_EXTRA_CHECKS
+#define Tbranch(t) assert(isbranch(t))
+#define Tleaf(t)  assert(!isbranch(t))
+#else
+#define Tbranch(t)
+#define Tleaf(t)
+#endif
+
+#define Tcheck_get(type, tag, field, expr) \
+	static inline type \
+	tag##_##field(Trie *t) \
+	{ \
+		tag(t); \
+		return expr; \
+	} \
+	struct dummy
+
+Tcheck_get(Trie *,       Tbranch, twigs, t->ptr);
+Tcheck_get(const char *, Tleaf,   key,   t->ptr);
+Tcheck_get(void *,       Tleaf,   val,   (void*)t->index);
+
+// index word layout
+
+#define Tix_width_branch 1
+#define Tix_width_shift  3
+#define Tix_width_offset 28
+#define Tix_width_bitmap 32
+
+#define Tix_base_branch 0
+#define Tix_base_shift  (Tix_base_branch + Tix_width_branch)
+#define Tix_base_offset (Tix_base_shift  + Tix_width_shift)
+#define Tix_base_bitmap (Tix_base_offset + Tix_width_offset)
+
+#define Tix_place(field) ((Tindex)(field) << Tix_base_##field)
+
+#define Tix_mask(field) ((1ULL << Tix_width_##field) - 1ULL)
+
+#define Tunmask(field,index) ((uint)(((index) >> Tix_base_##field)	\
+				     & Tix_mask(field)))
+
+#define Tmaxlen Tix_mask(offset)
+
+// index word accessor functions
+
+#define Tindex_get(type, field) \
+	static inline type \
+	Tindex_##field(Tindex i) \
+	{ \
+		return(Tunmask(field, i)); \
+	} \
+	struct dummy
+
+Tindex_get(bool, branch);
+Tindex_get(uint, shift);
+Tindex_get(uint, offset);
+Tindex_get(Tbitmap, bitmap);
+
+static inline Tindex
+Tindex_new(uint shift, uint offset, Tbitmap bitmap)
+{
+	uint branch = 1;
+	return( Tix_place(branch) |
+		Tix_place(shift)  |
+		Tix_place(offset) |
+		Tix_place(bitmap) );
+}
+
+static inline Tindex
+Tbitmap_add(Tindex i, Tbitmap bitmap)
+{
+	return(i | Tix_place(bitmap));
+}
+
+static inline Tindex
+Tbitmap_del(Tindex i, Tbitmap bitmap)
+{
+	return(i & ~Tix_place(bitmap));
+}
+
+// sanity checks!
+
+#if !defined(__plan9__)
+#if !defined(static_assert)
+#define static_assert_cat(a,b) a##b
+#define static_assert_name(line) static_assert_cat(static_assert_,line)
+#define static_assert(must_be_true,message)				\
+	static const void *static_assert_name(__LINE__)			\
+		[must_be_true ? 2 : -1] = {				\
+			message,					\
+			&static_assert_name(__LINE__) }
+#endif
+
+static_assert(Tix_base_bitmap + Tix_width_bitmap == 64,
+	      "index fields must fill a 64 bit word");
+
+static_assert(Tunmask(bitmap,0x1234567800000000ULL) == 0x12345678,
+	      "extracting the bitmap works");
+
+static_assert(Tunmask(offset,0x0420ULL) == 0x42,
+	      "extracting the offset works");
+
+static_assert(Tunmask(shift,0xFEDCBAULL) == 5,
+	      "extracting the shift works");
+
+//  ..key[o%5==0].. ..key[o%5==1].. ..key[o%5==2].. ..key[o%5==3].. ..key[o%5==4]..
+// |               |               |               |               |               |
+//  7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+// |         |         |         |         |         |         |         |         |
+//  shift=0   shift=5   shift=2   shift=7   shift=4   shift=1   shift=6   shift=3
+#endif
+
+static inline byte
+knybble(const char *key, uint off, uint shift)
+{
+	uint word = key[off]<<8;
+	if(word)
+		word |= key[off+1];
+	uint right = 16 - 5 - shift;
+	return (word >> right) & 0x1FU;
+}
+
+static inline byte
+nibble(Tindex i, const char *key, size_t len)
+{
+	uint off = Tindex_offset(i);
+	if(off >= len)
+		return(0);
+	else return knybble(key, off, Tindex_shift(i));
+}
+
+static inline Tbitmap
+twigbit(Tindex i, const char *key, size_t len)
+{
+	return(1U << nibble(i, key, len));
+}
+
+static inline bool
+hastwig(Tindex i, Tbitmap bit)
+{
+	return Tindex_bitmap(i) & bit;
+}
+
+static inline uint
+twigoff(Tindex i, Tbitmap bit)
+{
+	return popcount(Tindex_bitmap(i) & (bit-1));
+}
+
+#define TWIGOFFMAX(off, max, i, b) do{ \
+		off = twigoff(i, b); \
+		max = popcount(Tindex_bitmap(i)); \
+	}while(0)
--- /dev/null
+++ b/3rd/tbl.c
@@ -1,0 +1,57 @@
+// Tbl.c: simpler wrappers for core table functions
+//
+// Written by Tony Finch <dot@dotat.at>
+// You may do anything with this. It has no warranty.
+// <http://creativecommons.org/publicdomain/zero/1.0/>
+
+#include "platform.h"
+#include "tbl.h"
+
+void *
+Tgetl(Tbl *tbl, const char *key, size_t len)
+{
+	const char *rkey = nil;
+	void *rval = nil;
+	return Tgetkv(tbl, key, len, &rkey, &rval) ? rval : nil;
+}
+
+void *
+Tget(Tbl *tbl, const char *key)
+{
+	return Tgetl(tbl, key, strlen(key));
+}
+
+Tbl *
+Tset(Tbl *tbl, const char *key, void *value)
+{
+	return Tsetl(tbl, key, strlen(key), value);
+}
+
+Tbl *
+Tdell(Tbl *tbl, const char *key, size_t len)
+{
+	const char *rkey = nil;
+	void *rval = nil;
+	return Tdelkv(tbl, key, len, &rkey, &rval);
+}
+
+Tbl *
+Tdel(Tbl *tbl, const char *key)
+{
+	return Tdell(tbl, key, strlen(key));
+}
+
+bool
+Tnext(Tbl *tbl, const char **pkey, void **pvalue)
+{
+	size_t len = *pkey == nil ? 0 : strlen(*pkey);
+	return Tnextl(tbl, pkey, &len, pvalue);
+}
+
+const char *
+Tnxt(Tbl *tbl, const char *key)
+{
+	void *value = nil;
+	Tnext(tbl, &key, &value);
+	return key;
+}
--- /dev/null
+++ b/3rd/tbl.h
@@ -1,0 +1,56 @@
+// Tbl.h: an abstract API for tables with string keys.
+//
+// Written by Tony Finch <dot@dotat.at>
+// You may do anything with this. It has no warranty.
+// <http://creativecommons.org/publicdomain/zero/1.0/>
+
+#pragma once
+
+// A table is represented by a pointer to this incomplete struct type.
+// You initialize an empty table by setting the pointer to NULL.
+//
+typedef struct Tbl Tbl;
+#if defined(__plan9__)
+#pragma incomplete Tbl
+#endif
+
+// Get the value associated with a key.
+// Returns NULL if the key is not in the Table.
+//
+void *Tgetl(Tbl *tbl, const char *key, size_t klen);
+void *Tget(Tbl *tbl, const char *key);
+
+// Returns false if the key is not found, otherwise returns true and
+// sets *rkey and *rval to the table's key and value pointers.
+//
+bool Tgetkv(Tbl *tbl, const char *key, size_t klen, const char **rkey, void **rval);
+
+// Associate a key with a value in a table. Returns a new pointer to
+// the modified table. If there is an error it sets errno and returns
+// NULL. To delete a key, set its value to NULL. When the last key is
+// deleted, Tset() returns NULL without setting errno. The key and
+// value are borrowed not copied.
+//
+// Errors:
+// EINVAL - value pointer is not word-aligned
+// ENOMEM - allocation failed
+//
+Tbl *Tsetl(Tbl *tbl, const char *key, size_t klen, void *value);
+Tbl *Tset(Tbl *tbl, const char *key, void *value);
+Tbl *Tdell(Tbl *tbl, const char *key, size_t klen);
+Tbl *Tdel(Tbl *tbl, const char *key);
+
+// Deletes an entry from the table as above, and sets *rkey and *rval
+// to the removed key and value pointers.
+//
+Tbl *Tdelkv(Tbl *tbl, const char *key, size_t klen, const char **rkey, void **rval);
+
+// Find the next item in the table. The p... arguments are in/out
+// parameters. To find the first key, pass *pkey=NULL and *pklen=0.
+// For subsequent keys, *pkey must be present in the table and is
+// updated to the lexicographically following key. Returns false or
+// NULL when there are no more keys.
+//
+bool Tnextl(Tbl *tbl, const char **pkey, size_t *pklen, void **pvalue);
+bool Tnext(Tbl *tbl, const char **pkey, void **pvalue);
+const char *Tnxt(Tbl *tbl, const char *key);
--- a/LICENSE
+++ b/LICENSE
@@ -2,6 +2,8 @@
 
 SpookyHash implementation is under CC0 license.
 
+QP tries (3rd/fn.[ch] 3rd/tbl.[ch]) is under CC0 license.
+
 Copyright (c) 2008 Jeff Bezanson
 
 All rights reserved.
--- a/builtins.c
+++ b/builtins.c
@@ -184,17 +184,6 @@
 	return FL_void;
 }
 
-static void
-global_env_list(symbol_t *root, value_t *pv)
-{
-	while(root != nil){
-		if(root->name[0] != ':' && (root->binding != UNBOUND))
-			*pv = fl_cons(tagptr(root, TAG_SYM), *pv);
-		global_env_list(root->left, pv);
-		root = root->right;
-	}
-}
-
 BUILTIN("environment", environment)
 {
 	USED(args);
@@ -201,7 +190,12 @@
 	argcount(nargs, 0);
 	value_t lst = FL_nil;
 	fl_gc_handle(&lst);
-	global_env_list(FL(symtab), &lst);
+	const char *k = nil;
+	symbol_t *v;
+	while(Tnext(FL(symtab), &k, (void**)&v)){
+		if(v->binding != UNBOUND)
+			lst = fl_cons(tagptr(v, TAG_SYM), lst);
+	}
 	fl_free_gc_handles(1);
 	return lst;
 }
--- a/flisp.c
+++ b/flisp.c
@@ -197,24 +197,17 @@
 	return sym;
 }
 
-static symbol_t **
-symtab_lookup(symbol_t **ptree, const char *str)
-{
-	int x;
-	while(*ptree != nil && (x = strcmp(str, (*ptree)->name)) != 0)
-		ptree = x < 0 ? &(*ptree)->left : &(*ptree)->right;
-	return ptree;
-}
-
 value_t
 symbol(const char *str, bool copy)
 {
-	symbol_t **pnode;
-
-	pnode = symtab_lookup(&FL(symtab), str);
-	if(*pnode == nil)
-		*pnode = mk_symbol(str, copy);
-	return tagptr(*pnode, TAG_SYM);
+	int len = strlen(str);
+	symbol_t *v;
+	const char *k;
+	if(!Tgetkv(FL(symtab), str, len, &k, (void**)&v)){
+		v = mk_symbol(str, copy);
+		FL(symtab) = Tsetl(FL(symtab), v->name, len, v);
+	}
+	return tagptr(v, TAG_SYM);
 }
 
 BUILTIN("gensym", gensym)
@@ -434,13 +427,13 @@
 }
 
 static void
-trace_globals(symbol_t *root)
+trace_globals(void)
 {
-	while(root != nil){
-		if(root->binding != UNBOUND)
-			root->binding = relocate(root->binding);
-		trace_globals(root->left);
-		root = root->right;
+	const char *k = nil;
+	symbol_t *v;
+	while(Tnext(FL(symtab), &k, (void**)&v)){
+		if(v->binding != UNBOUND)
+			v->binding = relocate(v->binding);
 	}
 }
 
@@ -475,7 +468,7 @@
 	}
 	for(i = 0; i < FL(ngchandles); i++)
 		*FL(gchandles)[i] = relocate(*FL(gchandles)[i]);
-	trace_globals(FL(symtab));
+	trace_globals();
 	relocate_typetable();
 	rs = FL(readstate);
 	while(rs){
--- a/flisp.h
+++ b/flisp.h
@@ -3,6 +3,7 @@
 #include "platform.h"
 #include "utf8.h"
 #include "ios.h"
+#include "tbl.h"
 #include "bitvector.h"
 #include "htableh.inc"
 HTPROT(ptrhash)
@@ -57,11 +58,9 @@
 }cons_t;
 
 typedef struct _symbol_t {
-	fltype_t *type;
 	const char *name;
-	struct _symbol_t *left;
-	struct _symbol_t *right;
 	value_t binding;   // global value binding
+	fltype_t *type;
 	uint32_t hash;
 	uint8_t numtype;
 	uint8_t size;
@@ -365,7 +364,7 @@
 	uint32_t ngchandles;
 
 	fl_readstate_t *readstate;
-	symbol_t *symtab;
+	Tbl *symtab;
 
 	// saved execution state for an unwind target
 	fl_exception_context_t *exctx;
--- a/meson.build
+++ b/meson.build
@@ -33,8 +33,10 @@
 ]
 
 src = [
+	'3rd/fn.c',
 	'3rd/mt19937-64.c',
 	'3rd/spooky.c',
+	'3rd/tbl.c',
 	'bitvector.c',
 	'builtins.c',
 	'cvalues.c',
--- a/mkfile
+++ b/mkfile
@@ -12,9 +12,11 @@
 	plan9/platform.h\
 
 OFILES=\
+	3rd/fn.$O\
 	3rd/mt19937-64.$O\
-	3rd/wcwidth.$O\
 	3rd/spooky.$O\
+	3rd/tbl.$O\
+	3rd/wcwidth.$O\
 	bitvector.$O\
 	builtins.$O\
 	cvalues.$O\
@@ -30,6 +32,7 @@
 	main_plan9.$O\
 	opcodes.$O\
 	operators.$O\
+	plan9_builtins`{test -f plan9_builtins_$objtype.s && echo -n _$objtype}.$O\
 	print.$O\
 	ptrhash.$O\
 	random.$O\
--- a/plan9/platform.h
+++ b/plan9/platform.h
@@ -9,8 +9,10 @@
 #endif
 
 #define __os_name__ "plan9"
-
 #define __thread
+
+#define __builtin_prefetch(x)
+int __builtin_clz(unsigned int x);
 
 #define MEM_ALLOC(n) malloc(n)
 #define MEM_REALLOC(p, n) realloc((p), (n))
--- /dev/null
+++ b/plan9_builtins.c
@@ -1,0 +1,11 @@
+#include "platform.h"
+
+int
+__builtin_clz(unsigned int x)
+{
+	unsigned int r;
+	if(x == 0)
+		return 32;
+	for(r = 0; (x & (1UL<<31)) == 0; x <<= 1, r++);
+	return r;
+}
--- /dev/null
+++ b/plan9_builtins_amd64.s
@@ -1,0 +1,4 @@
+TEXT __builtin_clz(SB),1,$0
+	BYTE $0x0F; BYTE $0xBD; BYTE $0xC5 /* BSRL RARG, AX */
+	XORL $31, AX
+	RET
--- /dev/null
+++ b/plan9_builtins_arm64.s
@@ -1,0 +1,3 @@
+TEXT __builtin_clz(SB),1,$0
+	CLZW R0, R0
+	RETURN