shithub: sl

Download patch

ref: 35424e72a0a8b9069b3f24bb33b62892ca4b66d1
parent: b35c60cd64b6f630621163226d31f1e2fdc6c90d
parent: 3cfea02a4e7863afce7813a525fd6468be823846
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Fri Apr 4 23:53:27 EDT 2025

Merge branch 'unboxed'

--- a/boot/sl.boot
+++ b/boot/sl.boot
@@ -439,8 +439,9 @@
             self-evaluating? #fn("n120051S;3Z040H;36040RS;JK0421051;3A040R;3:04022051Q:" #(#fn(gensym?)
   #fn(const?) #fn(top-level-value)) self-evaluating?)
             separate-doc-from-body #fn("\x8710002000W1000J60q?14I2021?65140<0=2287513F01JB0883=07388871P62:13X02487513O086258751513B07388=8788<P1P62:761510P:" #(#0#
-  #fn("n1r520051L2;3a040KG21l2;3U040r2G22l2;3H040r3G23l2;3;040r4G24l2:" #(#fn(length) #\d #\o #\c
-                                                                          #\-) doc?)
+  #fn("n1r520051L2;3i04210K5222Q;3[04210r25223Q;3L04210r35224Q;3=04210r45225Q:" #(#fn(str-length)
+                                                                                  #fn(str-rune)
+                                                                                  #\d #\o #\c #\-) doc?)
   #fn(str?) separate-doc-from-body #fn(keyword?) #fn(str) reverse) separate-doc-from-body)
             set-syntax! #fn("n220710163:" #(#fn(put!) *syntax-environment*) set-syntax!) sort
             #fn("O200010003000W2000J7071?240=J400:0<7223182870>42418287>362:" #(#(:key 0) identity
@@ -468,7 +469,8 @@
                                                                                         #fn(str-length)
                                                                                         #fn(str-sub)) str-trim)
             sym-set-doc #fn("z220151873601@401<87360q@401=21Z3\xb40883\xaf0228823528:<8:=74258<528=;3H04268=5126778=28295351~8=;3?042:2;8>>18<52718;8?P23527<02=8@534893>07<02>8953@30q^1^1^1^1^1^1^1@30q482B3[07?02@527A2B8:>182527<02@2C8:8;5253^1^1@30q47D60:" #(#fn(str?)
-  str-join #fn(str-split) "\n" any #fn("n1E20051L2;3@040EG21l2;34040:" #(#fn(length) #\space))
+  str-join #fn(str-split) "\n" any #fn("n1E20051L2;3B04210E5222Q;34040:" #(#fn(str-length)
+                                                                           #fn(str-rune) #\space))
   #fn(length) str-trim " " "" #fn(map) #fn("n170A2105152390220A62:0:" #(<= #fn(length)
                                                                         #fn(str-sub))) putprop
   *doc* *doc-extra* getprop *formals-list* filter #fn("n1700A52S:" #(member))
--- a/src/builtins.c
+++ b/src/builtins.c
@@ -100,12 +100,12 @@
 		n += llength(v);
 		return size_wrap(n);
 	}
+	if(isrune(a))
+		return fixnum(runelen(torune(a)));
 	if(iscprim(a)){
 		cv = ptr(a);
 		if(cp_class(cv) == sl_utf8type)
 			return fixnum(1);
-		if(cp_class(cv) == sl_runetype)
-			return fixnum(runelen(*(Rune*)cp_data(cv)));
 	}
 	if(iscvalue(a) && cv_class(ptr(a))->eltype != nil)
 		return size_wrap(cvalue_arrlen(a));
@@ -226,7 +226,7 @@
 {
 	argcount(nargs, 1);
 	sl_v v = args[0];
-	if(isfixnum(v) || ismp(v))
+	if(isfixnum(v) || isubnum(v) || ismp(v))
 		return sl_t;
 	if(iscprim(v)){
 		sl_numtype nt = cp_numtype(ptr(v));
@@ -256,7 +256,7 @@
 {
 	argcount(nargs, 1);
 	sl_v v = args[0];
-	return (isfixnum(v) || ismp(v) ||
+	return (isfixnum(v) || isubnum(v) || ismp(v) ||
 			(iscprim(v) && cp_numtype(ptr(v)) < T_FLOAT)) ?
 		sl_t : sl_nil;
 }
@@ -274,6 +274,10 @@
 	sl_v v = args[0];
 	if(isfixnum(v))
 		return v;
+	if(isubnumu(v))
+		return fixnum(ubnumuval(v));
+	if(isubnums(v))
+		return fixnum(ubnumsval(v));
 	if(iscprim(v)){
 		void *p = ptr(v);
 		return fixnum(conv_to_s64(cp_data(p), cp_numtype(p)));
@@ -291,7 +295,7 @@
 {
 	argcount(nargs, 1);
 	sl_v v = args[0];
-	if(isfixnum(v) || ismp(v))
+	if(isfixnum(v) || isubnum(v) || ismp(v))
 		return v;
 	if(iscprim(v)){
 		sl_cprim *cp = ptr(v);
@@ -351,7 +355,11 @@
 todouble(sl_v a)
 {
 	if(isfixnum(a))
-		return (double)numval(a);
+		return numval(a);
+	if(isubnumu(a))
+		return ubnumuval(a);
+	if(isubnums(a))
+		return ubnumsval(a);
 	if(iscprim(a)){
 		sl_cprim *cp = ptr(a);
 		sl_numtype nt = cp_numtype(cp);
--- a/src/compress.c
+++ b/src/compress.c
@@ -17,7 +17,8 @@
 		type_error("arr", args[0]);
 	u8int *in;
 	usize insz;
-	to_sized_ptr(args[0], &in, &insz);
+	uintptr u;
+	to_sized_ptr(args[0], &in, &insz, &u);
 	int level = nargs > 1 ? tofixnum(args[1]) : 0;
 	if(level < 0)
 		level = 0;
@@ -47,7 +48,8 @@
 
 	u8int *in;
 	usize insz;
-	to_sized_ptr(args[0], &in, &insz);
+	uintptr u;
+	to_sized_ptr(args[0], &in, &insz, &u);
 	if(!isarr(args[0]))
 		type_error("arr", args[0]);
 	usize outsz;
@@ -59,7 +61,7 @@
 		out = cvalue_data(v);
 	}else if(args[1] == sl_tosym){
 		v = args[2];
-		to_sized_ptr(v, &out, &outsz);
+		to_sized_ptr(v, &out, &outsz, &u);
 	}else{
 		lerrorf(sl_errarg, "either :size or :to must be specified");
 	}
--- a/src/cvalues.c
+++ b/src/cvalues.c
@@ -15,6 +15,9 @@
 #define owned(cv) ((uintptr)(cv)->type & CV_OWNED)
 #define isinlined(cv) ((cv)->data == (cv)->_space)
 
+sl_type *unboxedtypes[T_UNBOXED_NUM];
+sl_v unboxedtypesyms[T_UNBOXED_NUM];
+
 static void cvalue_init(sl_type *type, sl_v v, void *dest);
 
 void
@@ -197,23 +200,52 @@
 	cv_autorelease(cv);
 }
 
+static void
+cvalue_rune_init(sl_type *type, sl_v a, void *dest)
+{
+	Rune r;
+	USED(type);
+	if(isfixnum(a)){
+		r = numval(a);
+	}else if(isrune(a)){
+		r = torune(a);
+	}else if(isubnumu(a)){
+		uintptr v = ubnumuval(a);
+		r = conv_to_u32(&v, ubnumtype(a));
+	}else if(isubnums(a)){
+		intptr v = ubnumsval(a);
+		r = conv_to_u32(&v, ubnumtype(a));
+	}else if(iscprim(a)){
+		sl_cprim *cp = ptr(a);
+		r = conv_to_u32(cp_data(cp), cp_numtype(cp));
+	}else if(ismp(a)){
+		r = conv_to_u32(cv_data(ptr(a)), T_MP);
+	}else
+		type_error("num", a);
+	*((Rune*)dest) = r;
+}
+
 #define num_init(ctype, cnvt, tag) \
 	static void \
-	cvalue_##ctype##_init(sl_type *type, sl_v arg, void *dest) \
+	cvalue_##ctype##_init(sl_type *type, sl_v a, void *dest) \
 	{ \
 		ctype n; \
 		USED(type); \
-		if(isfixnum(arg)) \
-			n = (ctype)numval(arg); \
-		else if(iscprim(arg)){ \
-			sl_cprim *cp = ptr(arg); \
-			void *p = cp_data(cp); \
-			n = (ctype)conv_to_##cnvt(p, cp_numtype(cp)); \
-		}else if(ismp(arg)){ \
-			void *p = cv_data(ptr(arg)); \
-			n = (ctype)conv_to_##cnvt(p, T_MP); \
+		if(isfixnum(a)){ \
+			n = (ctype)numval(a); \
+		}else if(isubnumu(a)){ \
+			uintptr v = ubnumuval(a); \
+			n = (ctype)conv_to_##cnvt(&v, ubnumtype(a)); \
+		}else if(isubnums(a)){ \
+			intptr v = ubnumsval(a); \
+			n = (ctype)conv_to_##cnvt(&v, ubnumtype(a)); \
+		}else if(iscprim(a)){ \
+			sl_cprim *cp = ptr(a); \
+			n = (ctype)conv_to_##cnvt(cp_data(cp), cp_numtype(cp)); \
+		}else if(ismp(a)){ \
+			n = (ctype)conv_to_##cnvt(cv_data(ptr(a)), T_MP); \
 		}else \
-			type_error("num", arg); \
+			type_error("num", a); \
 		*((ctype*)dest) = n; \
 	}
 
@@ -228,6 +260,17 @@
 num_init(float, double, T_FLOAT)
 num_init(double, double, T_DOUBLE)
 
+BUILTIN("rune", rune)
+{
+	if(nargs == 0){
+		PUSH(fixnum(0));
+		args = sl.sp-1;
+	}
+	Rune r;
+	cvalue_rune_init(sl_runetype, args[0], &r);
+	return mk_rune(r);
+}
+
 #define num_ctor_init(typenam, ctype, tag) \
 	static \
 	BUILTIN(#typenam, typenam) \
@@ -236,53 +279,102 @@
 			PUSH(fixnum(0)); \
 			args = sl.sp-1; \
 		} \
-		sl_v cp = cprim(sl_##typenam##type, sizeof(ctype)); \
-		cvalue_##ctype##_init(sl_##typenam##type, args[0], cp_data(ptr(cp))); \
-		return cp; \
+		sl_v v = cprim(sl_##typenam##type, sizeof(ctype)); \
+		cvalue_##ctype##_init(sl_##typenam##type, args[0], cp_data(ptr(v))); \
+		return v; \
 	}
 
+#define num_ctor_init_unboxed(typenam, ctype, tag) \
+	static \
+	BUILTIN(#typenam, typenam) \
+	{ \
+		if(nargs == 0){ \
+			PUSH(fixnum(0)); \
+			args = sl.sp-1; \
+		} \
+		sl_v v; \
+		if(tag < T_UNBOXED_NUM && \
+		   sizeof(ctype) < sizeof(sl_v)){ \
+			ctype u; \
+			cvalue_##ctype##_init(sl_##typenam##type, args[0], &u); \
+			v = (sl_v)u<<TAG_EXT_BITS | tag<<4 | TAG_UNBOXED; \
+		}else{ \
+			v = cprim(sl_##typenam##type, sizeof(ctype)); \
+			cvalue_##ctype##_init(sl_##typenam##type, args[0], cp_data(ptr(v))); \
+		} \
+		return v; \
+	}
+
 #define num_ctor_ctor(typenam, ctype, tag) \
-	sl_v mk_##typenam(ctype n) \
+	sl_v \
+	mk_##typenam(ctype n) \
 	{ \
-		sl_v cp = cprim(sl_##typenam##type, sizeof(ctype)); \
-		*(ctype*)cp_data(ptr(cp)) = n; \
-		return cp; \
+		sl_v v = cprim(sl_##typenam##type, sizeof(n)); \
+		*(ctype*)cp_data(ptr(v)) = n; \
+		return v; \
 	}
 
+#define num_ctor_ctor_unboxed(typenam, ctype, tag) \
+	sl_constfn \
+	sl_v \
+	mk_##typenam(ctype n) \
+	{ \
+		sl_v v; \
+		if(tag < T_UNBOXED_NUM && \
+		   (sizeof(n) < sizeof(sl_v) || fits_bits(n, UNBOXED_BITS))){ \
+			v = n<<TAG_EXT_BITS | tag<<4 | TAG_UNBOXED; \
+		}else{ \
+			v = cprim(sl_##typenam##type, sizeof(n)); \
+			*(ctype*)cp_data(ptr(v)) = n; \
+		} \
+		return v; \
+	}
+
 #define num_ctor(typenam, ctype, tag) \
 	num_ctor_init(typenam, ctype, tag) \
 	num_ctor_ctor(typenam, ctype, tag)
 
-num_ctor_init(s8, s8int, T_S8)
-num_ctor_init(u8, u8int, T_U8)
-num_ctor_init(s16, s16int, T_S16)
-num_ctor_init(u16, u16int, T_U16)
-num_ctor(s32, s32int, T_S32)
-num_ctor(u32, u32int, T_U32)
-num_ctor(s64, s64int, T_S64)
-num_ctor(u64, u64int, T_U64)
+#define num_ctor_unboxed(typenam, ctype, tag) \
+	num_ctor_init_unboxed(typenam, ctype, tag) \
+	num_ctor_ctor_unboxed(typenam, ctype, tag)
+
+num_ctor_init_unboxed(s8, s8int, T_S8)
+num_ctor_init_unboxed(u8, u8int, T_U8)
+num_ctor_init_unboxed(s16, s16int, T_S16)
+num_ctor_init_unboxed(u16, u16int, T_U16)
+num_ctor_unboxed(s32, s32int, T_S32)
+num_ctor_unboxed(u32, u32int, T_U32)
+num_ctor_unboxed(s64, s64int, T_S64)
+num_ctor_unboxed(u64, u64int, T_U64)
 num_ctor_init(utf8, u8int, T_U8)
 num_ctor(float, float, T_FLOAT)
 num_ctor(double, double, T_DOUBLE)
-num_ctor(rune, u32int, T_U32)
 
 static void
-cvalue_mp_init(sl_type *type, sl_v arg, void *dest)
+cvalue_mp_init(sl_type *type, sl_v a, void *dest)
 {
 	mpint *n;
 	USED(type);
-	if(isfixnum(arg)){
-		n = vtomp(numval(arg), nil);
-	}else if(iscvalue(arg)){
-		sl_cv *cv = ptr(arg);
+	if(isfixnum(a))
+		n = vtomp(numval(a), nil);
+	else if(isubnum(a)){
+		uintptr v;
+		void *p = &v;
+		if(isubnumu(a))
+			v = ubnumuval(a);
+		else
+			v = ubnumsval(a);
+		n = conv_to_mp(p, ubnumtype(a));
+	}else if(iscvalue(a)){
+		sl_cv *cv = ptr(a);
 		void *p = cv_data(cv);
 		n = conv_to_mp(p, cv_numtype(cv));
-	}else if(iscprim(arg)){
-		sl_cprim *cp = ptr(arg);
+	}else if(iscprim(a)){
+		sl_cprim *cp = ptr(a);
 		void *p = cp_data(cp);
 		n = conv_to_mp(p, cp_numtype(cp));
 	}else
-		type_error("num", arg);
+		type_error("num", a);
 	*((mpint**)dest) = n;
 }
 
@@ -316,6 +408,7 @@
 
 static sl_cvtable mp_vtable = { nil, nil, free_mp, nil };
 
+sl_constfn
 sl_v
 size_wrap(usize sz)
 {
@@ -330,6 +423,8 @@
 {
 	if(isfixnum(n))
 		return (usize)numval(n);
+	if(isubnum(n))
+		return (usize)ubnumuval(n);
 	if(iscprim(n)){
 		sl_cprim *cp = ptr(n);
 		if(sizeof(usize) == 8)
@@ -344,6 +439,10 @@
 {
 	if(isfixnum(n))
 		return numval(n);
+	if(isubnumu(n))
+		return ubnumuval(n);
+	if(isubnums(n))
+		return ubnumsval(n);
 	if(iscprim(n)){
 		sl_cprim *cp = ptr(n);
 		return conv_to_s64(cp_data(cp), cp_numtype(cp));
@@ -502,7 +601,9 @@
 {
 	sl_sym *s;
 
-	if(issym(type) && (s = ptr(type)) != nil && valid_numtype(s->numtype))
+	if(issym(type) &&
+	   (s = ptr(type)) != nil &&
+	   (valid_numtype(s->numtype) || type == sl_runesym))
 		return s->size;
 
 	if(iscons(type)){
@@ -522,8 +623,26 @@
 
 // get pointer and size for any plain-old-data value
 void
-to_sized_ptr(sl_v v, u8int **pdata, usize *psz)
+to_sized_ptr(sl_v v, u8int **pdata, usize *psz, uintptr *u)
 {
+	if(isubnumu(v)){
+		*u = ubnumuval(v);
+		*pdata = (u8int*)u;
+		*psz = unboxedtypes[ubnumtype(v)]->size;
+		return;
+	}
+	if(isubnums(v)){
+		*u = ubnumsval(v);
+		*pdata = (u8int*)u;
+		*psz = unboxedtypes[ubnumtype(v)]->size;
+		return;
+	}
+	if(isrune(v)){
+		Rune r = torune(v);
+		*pdata = (u8int*)u;
+		*psz = runetochar((char*)u, &r);
+		return;
+	}
 	if(iscvalue(v)){
 		sl_cv *pcv = ptr(v);
 		sl_ios *x;
@@ -554,7 +673,8 @@
 		return size_wrap(ctype_sizeof(args[0]));
 	usize n;
 	u8int *data;
-	to_sized_ptr(args[0], &data, &n);
+	uintptr u;
+	to_sized_ptr(args[0], &data, &n, &u);
 	return size_wrap(n);
 }
 
@@ -561,26 +681,33 @@
 sl_purefn
 BUILTIN("typeof", typeof)
 {
+	sl_v v = args[0];
 	argcount(nargs, 1);
-	switch(tag(args[0])){
+	switch(tag(v)){
 	case TAG_CONS: return sl_conssym;
 	case TAG_FIXNUM: return sl_fixnumsym;
+	case TAG_UNBOXED:
+		if(isubnum(v))
+			return unboxedtypesyms[ubnumtype(v)];
+		if(isrune(v))
+			return sl_runesym;
+		abort();
 	case TAG_SYM: return sl_symsym;
 	case TAG_VEC:return sl_vecsym;
 	case TAG_FN:
-		if(args[0] == sl_t)
+		if(v == sl_t)
 			return sl_booleansym;
-		if(args[0] == sl_nil)
+		if(v == sl_nil)
 			return sl_nullsym;
-		if(args[0] == sl_eof)
+		if(v == sl_eof)
 			return sl_eof;
-		if(args[0] == sl_void)
+		if(v == sl_void)
 			return sl_void;
-		if(isbuiltin(args[0]))
+		if(isbuiltin(v))
 			return sl_builtinsym;
 		return sl_fnsym;
 	}
-	return cv_type(ptr(args[0]));
+	return cv_type(ptr(v));
 }
 
 sl_v
@@ -647,7 +774,9 @@
 BUILTIN("plain-old-data?", plain_old_datap)
 {
 	argcount(nargs, 1);
-	return (iscprim(args[0]) ||
+	return (isubnum(args[0]) ||
+	        iscprim(args[0]) ||
+	        isrune(args[0]) ||
 			(iscvalue(args[0]) && cv_isPOD(ptr(args[0])))) ?
 		sl_t : sl_nil;
 }
@@ -825,6 +954,7 @@
 
 #define RETURN_NUM_AS(var, type) return(mk_##type(var))
 
+sl_constfn
 sl_v
 return_from_u64(u64int Uaccum)
 {
@@ -927,6 +1057,19 @@
 		*pp = pi;
 		*pt = T_FIXNUM;
 		return true;
+	}else if(isubnum(a)){
+		if(isubnumu(a))
+			*pi = ubnumuval(a);
+		else
+			*pi = ubnumsval(a);
+		*pp = pi;
+		*pt = ubnumtype(a);
+		return true;
+	}else if(isrune(a)){
+		*pi = torune(a);
+		*pp = pi;
+		*pt = T_U32;
+		return true;
 	}else if(iscprim(a)){
 		cp = ptr(a);
 		*pp = cp_data(cp);
@@ -1215,15 +1358,15 @@
 
 	if(isfixnum(a))
 		return fixnum(~numval(a));
+	if(isubnumu(a))
+		return (~ubnumuval(a) & ~0xff) | (a & 0xff);
+	if(isubnums(a))
+		return (~ubnumsval(a) & ~0xff) | (a & 0xff);
 	if(iscprim(a)){
 		cp = ptr(a);
 		ta = cp_numtype(cp);
 		aptr = cp_data(cp);
 		switch(ta){
-		case T_S8:  return fixnum(~*(s8int *)aptr);
-		case T_U8:  return fixnum(~*(u8int *)aptr & 0xff);
-		case T_S16: return fixnum(~*(s16int*)aptr);
-		case T_U16: return fixnum(~*(u16int*)aptr & 0xffff);
 		case T_S32: return mk_s32(~*(s32int*)aptr);
 		case T_U32: return mk_u32(~*(u32int*)aptr);
 		case T_S64: return mk_s64(~*(s64int*)aptr);
@@ -1248,6 +1391,7 @@
 {
 	sl_fx n;
 	s64int accum;
+	u64int u;
 	sl_cprim *cp;
 	int ta;
 	mpint *mp;
@@ -1259,8 +1403,8 @@
 	if(n == 0)
 		return a;
 	mp = nil;
-	if(isfixnum(a)){
-		accum = numval(a);
+	if(isfixnum(a) || isubnums(a)){
+		accum = isfixnum(a) ? numval(a) : ubnumsval(a);
 		if(n > -64 && n < 0)
 			return fixnum(accum>>(-n));
 		if(n < 0 || n >= 64 || sash_overflow_64(accum, n, &accum)){
@@ -1269,6 +1413,15 @@
 		}else
 			return fits_fixnum(accum) ? fixnum(accum) : return_from_s64(accum);
 	}
+	if(isubnumu(a)){
+		u = ubnumuval(a);
+		if(n > 0)
+			u = n < 64 ? u<<n : 0;
+		else
+			u = n > -64 ? u>>-n : 0;
+		accum = u;
+		return (accum >= 0 && fits_fixnum(accum)) ? fixnum(accum) : return_from_u64(u);
+	}
 	if(iscprim(a)){
 		cp = ptr(a);
 		ta = cp_numtype(cp);
@@ -1276,10 +1429,6 @@
 		if(n < 0){
 			n = -n;
 			switch(ta){
-			case T_S8:  return fixnum((*(s8int *)aptr) >> n);
-			case T_U8:  return fixnum((*(u8int *)aptr) >> n);
-			case T_S16: return fixnum((*(s16int*)aptr) >> n);
-			case T_U16: return fixnum((*(u16int*)aptr) >> n);
 			case T_S32: return mk_s32((*(s32int*)aptr) >> n);
 			case T_U32: return mk_u32((*(u32int*)aptr) >> n);
 			case T_S64: return mk_s64((*(s64int*)aptr) >> n);
@@ -1312,6 +1461,13 @@
 	htable_new(&slg.types, 256);
 	htable_new(&slg.reverse_dlsym_lookup, 256);
 
+	ctor_cv_intern(rune, NONNUMERIC, u32int);
+	sl_sym *sym = ptr(sl_runesym);
+	sym->numtype = NONNUMERIC;
+	sym->size = sizeof(Rune);
+	sl_runetype = get_type(sl_runesym);
+	sl_runetype->init = cvalue_rune_init;
+
 	sl_builtintype = define_opaque_type(sl_builtinsym, sizeof(builtin_t), nil, nil);
 
 	ctor_cv_intern(s8, T_S8, s8int);
@@ -1323,7 +1479,6 @@
 	ctor_cv_intern(s64, T_S64, s64int);
 	ctor_cv_intern(u64, T_U64, u64int);
 	ctor_cv_intern(utf8, T_U8, u8int);
-	ctor_cv_intern(rune, T_U32, u32int);
 	ctor_cv_intern(float, T_FLOAT, float);
 	ctor_cv_intern(double, T_DOUBLE, double);
 
@@ -1338,7 +1493,6 @@
 	mk_primtype(s64, s64int);
 	mk_primtype(u64, u64int);
 	mk_primtype(utf8, u8int);
-	mk_primtype(rune, u32int);
 	mk_primtype(float, float);
 	mk_primtype(double, double);
 
@@ -1346,6 +1500,23 @@
 	sl_mptype = get_type(sl_bignumsym);
 	sl_mptype->init = cvalue_mp_init;
 	sl_mptype->vtable = &mp_vtable;
+
+	unboxedtypes[T_S8] = sl_s8type;
+	unboxedtypes[T_U8] = sl_u8type;
+	unboxedtypes[T_S16] = sl_s16type;
+	unboxedtypes[T_U16] = sl_u16type;
+	unboxedtypes[T_S32] = sl_s32type;
+	unboxedtypes[T_U32] = sl_u32type;
+	unboxedtypes[T_S64] = sl_s64type;
+	unboxedtypes[T_U64] = sl_u64type;
+	unboxedtypesyms[T_S8] = sl_s8sym;
+	unboxedtypesyms[T_U8] = sl_u8sym;
+	unboxedtypesyms[T_S16] = sl_s16sym;
+	unboxedtypesyms[T_U16] = sl_u16sym;
+	unboxedtypesyms[T_S32] = sl_s32sym;
+	unboxedtypesyms[T_U32] = sl_u32sym;
+	unboxedtypesyms[T_S64] = sl_s64sym;
+	unboxedtypesyms[T_U64] = sl_u64sym;
 
 	sl_strtype = get_type(mk_list2(sl_arrsym, sl_utf8sym));
 	sl_emptystr = cvalue_from_ref(sl_strtype, (char*)"", 0);
--- a/src/cvalues.h
+++ b/src/cvalues.h
@@ -26,7 +26,7 @@
 void cvalue_arr_init(sl_type *ft, sl_v arg, void *dest);
 usize cvalue_arrlen(sl_v v) sl_purefn;
 usize ctype_sizeof(sl_v type);
-void to_sized_ptr(sl_v v, u8int **pdata, usize *psz);
+void to_sized_ptr(sl_v v, u8int **pdata, usize *psz, uintptr *u);
 sl_v cvalue_relocate(sl_v v);
 sl_v cvalue_copy(sl_v v);
 sl_v cvalue_compare(sl_v a, sl_v b) sl_purefn;
@@ -50,7 +50,6 @@
 sl_v mk_u32(u32int n);
 sl_v mk_s64(s64int n);
 sl_v mk_u64(u64int n);
-sl_v mk_rune(Rune n);
 sl_v mk_mp(mpint *n);
 
 usize llength(sl_v v) sl_purefn;
--- a/src/equal.c
+++ b/src/equal.c
@@ -15,9 +15,6 @@
 #define doublehash(a) int64to32hash(a)
 #endif
 
-// comparable tag
-#define cmptag(v) (isfixnum(v) ? TAG_FIXNUM : tag(v))
-
 static sl_v
 eq_class(sl_htable *table, sl_v key)
 {
@@ -77,22 +74,24 @@
 	if(bound <= 0)
 		return sl_nil;
 	int taga = tag(a);
-	int tagb = cmptag(b);
+	int tagb = tag(b);
 	int c;
 	switch(taga){
 	case TAG_FIXNUM:
 		if(isfixnum(b))
 			return (sl_fx)a < (sl_fx)b ? fixnum(-1) : fixnum(1);
-		if(iscprim(b)){
-			if(cp_class(ptr(b)) == sl_runetype)
+		if(isubnum(b) || iscprim(b) || (iscvalue(b) && (cv = ptr(b), valid_numtype(cv_numtype(cv)))))
+			return fixnum(numeric_compare(a, b, eq, true, false));
+		if(isrune(b))
 				return fixnum(1);
+		return fixnum(-1);
+	case TAG_UNBOXED:
+		if(isrune(a))
+			return fixnum(isrune(b) && a == b ? 0 : -1);
+		if(isrune(b))
+				return fixnum(1);
+		if(isfixnum(b) || isubnum(b) || iscprim(b) || (iscvalue(b) && (cv = ptr(b), valid_numtype(cv_numtype(cv)))))
 			return fixnum(numeric_compare(a, b, eq, true, false));
-		}
-		if(iscvalue(b)){
-			cv = ptr(b);
-			if(valid_numtype(cv_numtype(cv)))
-				return fixnum(numeric_compare(a, b, eq, true, false));
-		}
 		return fixnum(-1);
 	case TAG_SYM:
 		if(eq || tagb < TAG_SYM)
@@ -105,11 +104,6 @@
 			return bounded_vec_compare(a, b, bound, eq);
 		break;
 	case TAG_CPRIM:
-		if(cp_class(ptr(a)) == sl_runetype){
-			if(!iscprim(b) || cp_class(ptr(b)) != sl_runetype)
-				return fixnum(-1);
-		}else if(iscprim(b) && cp_class(ptr(b)) == sl_runetype)
-			return fixnum(1);
 		c = numeric_compare(a, b, eq, true, false);
 		if(c != 2)
 			return fixnum(c);
@@ -334,6 +328,16 @@
 	case TAG_FIXNUM:
 		u.d = (double)numval(a);
 		return doublehash(u.i64);
+	case TAG_UNBOXED:
+		if(isubnumu(a))
+			u.d = ubnumuval(a);
+		else if(isubnums(a))
+			u.d = ubnumsval(a);
+		else if(isrune(a))
+			return inthash(torune(a));
+		else
+			abort();
+		return doublehash(u.i64);
 	case TAG_FN:
 		if(uintval(a) > N_BUILTINS)
 			return bounded_hash(((sl_fn*)ptr(a))->bcode, bound, oob);
@@ -343,8 +347,6 @@
 	case TAG_CPRIM:
 		cp = ptr(a);
 		data = cp_data(cp);
-		if(cp_class(cp) == sl_runetype)
-			return inthash(*(Rune*)data);
 		nt = cp_numtype(cp);
 		u.d = conv_to_double(data, nt);
 		return doublehash(u.i64);
--- a/src/io.c
+++ b/src/io.c
@@ -196,11 +196,9 @@
 {
 	argcount(nargs, 2);
 	sl_ios *s = toio(args[0]);
-	sl_cprim *cp = ptr(args[1]);
-	if(!iscprim(args[1]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[1]))
 		type_error("rune", args[1]);
-	Rune r = *(Rune*)cp_data(cp);
-	return fixnum(ios_putrune(s, r));
+	return fixnum(ios_putrune(s, torune(args[1])));
 }
 
 BUILTIN("io-skip", io_skip)
@@ -325,16 +323,15 @@
 		argcount(nargs, 2);
 	sl_ios *s = toio(args[0]);
 	sl_v v = args[1];
-	sl_cprim *cp = ptr(v);
-	if(iscprim(args[1]) && cp_class(cp) == sl_runetype){
+	if(isrune(v)){
 		if(nargs > 2)
 			lerrorf(sl_errarg, "offset argument not supported for characters");
-		Rune r = *(Rune*)cp_data(ptr(args[1]));
-		return fixnum(ios_putrune(s, r));
+		return fixnum(ios_putrune(s, torune(v)));
 	}
 	u8int *data;
 	usize sz, offs = 0;
-	to_sized_ptr(v, &data, &sz);
+	uintptr u;
+	to_sized_ptr(v, &data, &sz, &u);
 	usize nb = sz;
 	if(nargs > 2){
 		get_start_count_args(args+1, nargs-1, sz, &offs, &nb);
@@ -346,10 +343,10 @@
 static u8int
 get_delim_arg(sl_v arg)
 {
-	usize uldelim = tosize(arg);
+	usize uldelim = isrune(arg) ? torune(arg) : tosize(arg);
 	if(uldelim > 0x7f){
 		// runes > 0x7f, or anything else > 0xff, are out of range
-		if((iscprim(arg) && cp_class(ptr(arg)) == sl_runetype) || uldelim > 0xff)
+		if(isrune(arg) || uldelim > 0xff)
 			lerrorf(sl_errarg, "delimiter out of range");
 	}
 	return (u8int)uldelim;
--- a/src/plan9/lsd.c
+++ b/src/plan9/lsd.c
@@ -242,8 +242,8 @@
 
 	argcount(nargs, 3);
 	for(a = args; a < args+3; a++)
-	if(sl_unlikely(!sl_isnum(*a)))
-		type_error("num", *a);
+		if(sl_unlikely(!sl_isnum(*a)))
+			type_error("num", *a);
 
 	pc = tosize(args[0]);
 	sp = tosize(args[1]);
@@ -277,8 +277,8 @@
 	if(sl_unlikely(!sl_isstr(args[0]) && !sl_isnum(args[0])))
 		type_error("str|num", args[0]);
 
-	if(isfixnum(args[0])){
-		pid = numval(args[0]);
+	if(sl_isnum(args[0])){
+		pid = tosize(args[0]);
 		snprint(aout, sizeof(aout), "/proc/%d/text", pid);
 	}else{
 		len = cv_len(ptr(args[0]));
--- a/src/print.c
+++ b/src/print.c
@@ -187,7 +187,7 @@
 	if(sl_isstr(v))
 		return cv_len(ptr(v)) < SMALL_STR_LEN;
 	return (
-		isfixnum(v) || isbuiltin(v) || iscprim(v) ||
+		isfixnum(v) || isunboxed(v) || isbuiltin(v) || iscprim(v) ||
 		v == sl_t || v == sl_nil || v == sl_eof || v == sl_void
 	);
 }
@@ -233,7 +233,7 @@
 		const char *s = sym_name(v);
 		return u8_strwidth(s, strlen(s));
 	}
-	if(iscprim(v) && ptr(v) != nil && cp_class(ptr(v)) == sl_runetype)
+	if(isrune(v))
 		return 4;
 	return -1;
 }
@@ -385,7 +385,9 @@
 	}
 }
 
+static void unboxed_print(sl_ios *f, sl_v v);
 static void cvalue_print(sl_ios *f, sl_v v);
+static void rune_print(sl_ios *f, Rune r);
 
 static bool
 print_circle_prefix(sl_ios *f, sl_v v)
@@ -427,6 +429,9 @@
 			lerrorf(sl_errio, "write failed");
 		sl.hpos += n;
 		break;
+	case TAG_UNBOXED:
+		unboxed_print(f, v);
+		break;
 	case TAG_SYM:
 		name = sym_name(v);
 		if(sl.print_princ)
@@ -657,6 +662,45 @@
 	}
 }
 
+static void
+rune_print(sl_ios *f, Rune r)
+{
+	char seq[UTFmax+1];
+	int n, nb = runetochar(seq, &r);
+	seq[nb] = '\0';
+	if(sl.print_princ){
+		outsn(f, seq, nb);
+	}else{
+		outsc(f, "#\\");
+		switch(r){
+		case 0x00: outsc(f, "nul"); break;
+		case 0x07: outsc(f, "alarm"); break;
+		case 0x08: outsc(f, "backspace"); break;
+		case 0x09: outsc(f, "tab"); break;
+		case 0x0a: outsc(f, "newline"); break;
+		case 0x0b: outsc(f, "vtab"); break;
+		case 0x0c: outsc(f, "page"); break;
+		case 0x0d: outsc(f, "return"); break;
+		case 0x1b: outsc(f, "esc"); break;
+		case ' ':  outsc(f, "space"); break;
+		case 0x7f: outsc(f, "delete"); break;
+		default:
+			if(sl_iswprint(r))
+				outs(f, seq);
+			else{
+				n = ios_printf(f, "x%04"PRIx32, r);
+				if(n < 1)
+					goto err;
+				sl.hpos += n;
+			}
+			break;
+		}
+	}
+	return;
+err:
+	lerrorf(sl_errio, "write failed");
+}
+
 // 'weak' means we don't need to accurately reproduce the type, so
 // for example #s32(0) can be printed as just 0. this is used
 // printing in a context where a type is already implied, e.g. inside
@@ -679,38 +723,7 @@
 			sl.hpos += n;
 		}
 	}else if(type == sl_runesym){
-		Rune r = *(Rune*)data;
-		char seq[UTFmax+1];
-		int nb = runetochar(seq, &r);
-		seq[nb] = '\0';
-		if(sl.print_princ){
-			outsn(f, seq, nb);
-		}else{
-			outsc(f, "#\\");
-			switch(r){
-			case 0x00: outsc(f, "nul"); break;
-			case 0x07: outsc(f, "alarm"); break;
-			case 0x08: outsc(f, "backspace"); break;
-			case 0x09: outsc(f, "tab"); break;
-			case 0x0a: outsc(f, "newline"); break;
-			case 0x0b: outsc(f, "vtab"); break;
-			case 0x0c: outsc(f, "page"); break;
-			case 0x0d: outsc(f, "return"); break;
-			case 0x1b: outsc(f, "esc"); break;
-			case ' ':  outsc(f, "space"); break;
-			case 0x7f: outsc(f, "delete"); break;
-			default:
-				if(sl_iswprint(r))
-					outs(f, seq);
-				else{
-					n = ios_printf(f, "x%04"PRIx32, r);
-					if(n < 1)
-						goto err;
-					sl.hpos += n;
-				}
-				break;
-			}
-		}
+		rune_print(f, *(Rune*)data);
 	}else if(type == sl_floatsym || type == sl_doublesym){
 		char buf[64];
 		double d;
@@ -849,6 +862,26 @@
 }
 
 static void
+unboxed_print(sl_ios *f, sl_v v)
+{
+	uintptr u;
+
+	if(isubnumu(v))
+		u = ubnumuval(v);
+	else if(isubnums(v))
+		u = ubnumsval(v);
+	else if(isrune(v)){
+		rune_print(f, torune(v));
+		return;
+	}else
+		abort();
+	int numtype = ubnumtype(v);
+	sl_v typesym = unboxedtypesyms[numtype];
+	sl_type *type = unboxedtypes[numtype];
+	cvalue_printdata(f, &u, type->size, typesym, 0);
+}
+
+static void
 cvalue_print(sl_ios *f, sl_v v)
 {
 	sl_cv *cv = ptr(v);
@@ -877,10 +910,13 @@
 static void
 set_print_width(void)
 {
-	sl_v pw = sym_value(sl_printwidthsym);
-	if(!isfixnum(pw))
-		return;
-	sl.scr_width = numval(pw);
+	sl_v v = sym_value(sl_printwidthsym);
+	if(isfixnum(v))
+		sl.scr_width = numval(v);
+	else if(isubnumu(v))
+		sl.scr_width = ubnumuval(v);
+	else if(isubnums(v))
+		sl.scr_width = ubnumsval(v);
 }
 
 void
@@ -891,9 +927,9 @@
 		set_print_width();
 	sl.print_princ = sym_value(sl_printreadablysym) == sl_nil;
 	sl_v pl = sym_value(sl_printlengthsym);
-	sl.print_length = isfixnum(pl) ? numval(pl) : -1;
+	sl.print_length = sl_isnum(pl) ? numtofx(pl) : -1;
 	pl = sym_value(sl_printlevelsym);
-	sl.print_level = isfixnum(pl) ? numval(pl) : -1;
+	sl.print_level = sl_isnum(pl) ? numtofx(pl) : -1;
 	sl.p_level = 0;
 
 	sl.printlabel = 0;
--- a/src/sl.c
+++ b/src/sl.c
@@ -385,7 +385,7 @@
 {
 	sl_v a, d, nc, first, *pcdr;
 
-	if(isfixnum(v))
+	if(isfixnum(v) || isunboxed(v))
 		return v;
 
 	uintptr t = tag(v);
@@ -692,12 +692,16 @@
 bool
 sl_isnum(sl_v v)
 {
-	if(isfixnum(v) || ismp(v))
+	if(isfixnum(v))
 		return true;
+	if(isubnum(v))
+		return true;
 	if(iscprim(v)){
 		sl_cprim *c = ptr(v);
-		return c->type != sl_runetype && valid_numtype(c->type->numtype);
+		return valid_numtype(c->type->numtype);
 	}
+	if(ismp(v))
+		return true;
 	return false;
 }
 
--- a/src/sl.h
+++ b/src/sl.h
@@ -15,11 +15,14 @@
 	TAG_CPRIM,
 	TAG_FN,
 	TAG_VEC,
-	TAG_EXT,
+	TAG_UNBOXED,
 	TAG_CVALUE,
 	TAG_SYM,
 	TAG_CONS,
 
+	TAG_BITS = 3,
+	TAG_EXT_BITS = 8,
+
 	/* those were set to 7 and 3 strategically on purpose */
 	TAG_NONLEAF_MASK = TAG_CONS & TAG_VEC,
 };
@@ -34,9 +37,9 @@
 	T_S16, T_U16,
 	T_S32, T_U32,
 	T_S64, T_U64,
-	T_MP,
-	T_FLOAT,
-	T_DOUBLE,
+	T_UNBOXED_NUM,
+	T_MP = T_UNBOXED_NUM,
+	T_FLOAT, T_DOUBLE,
 }sl_numtype;
 
 typedef uintptr sl_v;
@@ -43,13 +46,15 @@
 
 #if defined(BITS64)
 typedef s64int sl_fx;
-#define FIXNUM_BITS 61
+#define FIXNUM_BITS (64-TAG_BITS)
+#define UNBOXED_BITS (64-TAG_EXT_BITS)
 #define TOP_BIT (1ULL<<63)
 #define T_FIXNUM T_S64
 #define PRIdFIXNUM PRId64
 #else
 typedef s32int sl_fx;
-#define FIXNUM_BITS 29
+#define FIXNUM_BITS (32-TAG_BITS)
+#define UNBOXED_BITS (32-TAG_EXT_BITS)
 #define TOP_BIT (1U<<31)
 #define T_FIXNUM T_S32
 #define PRIdFIXNUM PRId32
@@ -100,12 +105,15 @@
 #define valid_numtype(v) ((v) <= T_DOUBLE)
 #define UNBOUND ((sl_v)1) // an invalid value
 #define tag(x) ((x) & 7)
+#define tagext(x) ((x) & 0xff)
 #define ptr(x) ((void*)((uintptr)(x) & (~(uintptr)7)))
 #define tagptr(p, t) ((sl_v)(p) | (t))
-#define fixnum(x) ((sl_v)(x)<<3)
-#define numval(x)  ((sl_fx)(x)>>3)
-#define uintval(x) (((unsigned int)(x))>>3)
-#define builtin(n) tagptr(((sl_v)n<<3), TAG_FN)
+#define fixnum(x) ((sl_v)(x)<<TAG_BITS)
+#define numval(x) ((sl_fx)(x)>>TAG_BITS)
+#define ubnumsval(x) ((intptr)(x)>>(TAG_BITS+1+4))
+#define ubnumuval(x) ((uintptr)(x)>>(TAG_BITS+1+4))
+#define uintval(x) (((unsigned int)(x))>>TAG_BITS)
+#define builtin(n) tagptr(((sl_v)n<<TAG_BITS), TAG_FN)
 #define iscons(x) (tag(x) == TAG_CONS)
 #define issym(x) (tag(x) == TAG_SYM)
 #define isfixnum(x) (tag(x) == TAG_FIXNUM)
@@ -113,9 +121,27 @@
 #define isvec(x) (tag(x) == TAG_VEC)
 #define iscvalue(x) (tag(x) == TAG_CVALUE)
 #define iscprim(x) (tag(x) == TAG_CPRIM)
+#define isunboxed(x) (tag(x) == TAG_UNBOXED)
 // doesn't lead to other values
 #define leafp(a) (((a)&TAG_NONLEAF_MASK) != TAG_NONLEAF_MASK)
 
+/* UNBOXED
+ * integers: ...|xxxxxxxx|xxxxxxxx|xxxxxxxx|tttt0100|
+ * runes:    ...|xxxxxxxx|xxxxxxxx|xxxxxxxx|11111100|
+ */
+#define TAG_UBNUM_SHIFT (TAG_BITS+1)
+#define isubnum(x)  ((tagext(x) & ((1<<TAG_UBNUM_SHIFT)-1)) == (0<<TAG_BITS | TAG_UNBOXED))
+#define isubnums(x) ((tagext(x) & ((2<<TAG_UBNUM_SHIFT)-1)) == (0<<TAG_BITS | TAG_UNBOXED))
+#define isubnumu(x) ((tagext(x) & ((2<<TAG_UBNUM_SHIFT)-1)) == (2<<TAG_BITS | TAG_UNBOXED))
+#define ubnumtype(x) ((tagext(x)>>TAG_UBNUM_SHIFT) & 0xf)
+#define numtofx(v) (sl_fx)tooffset(v)
+extern sl_type *unboxedtypes[T_UNBOXED_NUM];
+extern sl_v unboxedtypesyms[T_UNBOXED_NUM];
+
+#define mk_rune(r) ((r)<<TAG_EXT_BITS | 0xfc)
+#define isrune(v) (((v) & 0xff) == 0xfc)
+#define torune(v) ((v)>>8)
+
 // allocate n consecutive conses
 #define cons_reserve(n) tagptr(alloc_words((n)*2), TAG_CONS)
 #define cons_index(c) (((sl_cons*)ptr(c))-((sl_cons*)slg.fromspace))
@@ -130,8 +156,8 @@
 		*(sl_v*)ptr(v) = (sl_v)(to) | FWD_BIT; \
 	}while(0)
 
-#define vec_size(v) (((usize*)ptr(v))[0]>>3)
-#define vec_setsize(v, n) (((usize*)ptr(v))[0] = ((n)<<3))
+#define vec_size(v) (((usize*)ptr(v))[0]>>TAG_BITS)
+#define vec_setsize(v, n) (((usize*)ptr(v))[0] = ((n)<<TAG_BITS))
 #define vec_elt(v, i) (((sl_v*)ptr(v))[1+(i)])
 #define vec_grow_amt(x) ((x)<8 ? 5 : 6*((x)>>3))
 // functions ending in _ are unsafe, faster versions
@@ -157,7 +183,7 @@
 #define sym_to_numtype(s) (((sl_sym*)ptr(s))->numtype)
 #define ismanaged(v) ((((u8int*)ptr(v)) >= slg.fromspace) && (((u8int*)ptr(v)) < slg.fromspace+slg.heapsize))
 #define isgensym(x)  (issym(x) && ismanaged(x))
-#define isfn(x) (tag(x) == TAG_FN && (x) > (N_BUILTINS<<3))
+#define isfn(x) (tag(x) == TAG_FN && (x) > (N_BUILTINS<<TAG_BITS))
 #define iscbuiltin(x) (iscvalue(x) && cv_class(ptr(x)) == sl_builtintype)
 // utility for iterating over all arguments in a builtin
 // i=index, i0=start index, arg = var for each arg, args = arg array
@@ -279,7 +305,7 @@
 	void (*relocate)(sl_v oldv, sl_v newv);
 	void (*finalize)(sl_v self);
 	void (*print_traverse)(sl_v self);
-} sl_cvtable;
+}sl_cvtable;
 
 typedef void (*cvinitfunc_t)(sl_type*, sl_v, void*);
 
--- a/src/sl_arith_any.h
+++ b/src/sl_arith_any.h
@@ -20,7 +20,15 @@
 		if(isfixnum(arg))
 			x = numval(arg);
 		else{
-			if(iscprim(arg)){
+			if(isubnumu(arg)){
+				u64 = ubnumuval(arg);
+				a = &u64;
+				pt = ubnumtype(arg);
+			}else if(isubnums(arg)){
+				u64 = ubnumsval(arg);
+				a = &u64;
+				pt = ubnumtype(arg);
+			}else if(iscprim(arg)){
 				cp = ptr(arg);
 				a = cp_data(cp);
 				pt = cp_numtype(cp);
--- a/src/str.c
+++ b/src/str.c
@@ -44,12 +44,9 @@
 BUILTIN("str-width", str_width)
 {
 	argcount(nargs, 1);
-	if(iscprim(args[0])){
-		sl_cprim *cp = ptr(args[0]);
-		if(cp_class(cp) == sl_runetype){
-			int w = sl_wcwidth(*(Rune*)cp_data(cp));
-			return w < 0 ? sl_nil : fixnum(w);
-		}
+	if(isrune(args[0])){
+		int w = sl_wcwidth(torune(args[0]));
+		return w < 0 ? sl_nil : fixnum(w);
 	}
 	if(!sl_isstr(args[0]))
 		type_error("str", args[0]);
@@ -200,31 +197,31 @@
 	return mk_rune(r);
 }
 
+sl_purefn
 BUILTIN("rune-upcase", rune_upcase)
 {
 	argcount(nargs, 1);
-	sl_cprim *cp = ptr(args[0]);
-	if(!iscprim(args[0]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[0]))
 		type_error("rune", args[0]);
-	return mk_rune(toupperrune(*(Rune*)cp_data(cp)));
+	return mk_rune(toupperrune(torune(args[0])));
 }
 
+sl_purefn
 BUILTIN("rune-downcase", rune_downcase)
 {
 	argcount(nargs, 1);
-	sl_cprim *cp = ptr(args[0]);
-	if(!iscprim(args[0]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[0]))
 		type_error("rune", args[0]);
-	return mk_rune(tolowerrune(*(Rune*)cp_data(cp)));
+	return mk_rune(tolowerrune(torune(args[0])));
 }
 
+sl_purefn
 BUILTIN("rune-titlecase", rune_titlecase)
 {
 	argcount(nargs, 1);
-	sl_cprim *cp = ptr(args[0]);
-	if(!iscprim(args[0]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[0]))
 		type_error("rune", args[0]);
-	return mk_rune(totitlerune(*(Rune*)cp_data(cp)));
+	return mk_rune(totitlerune(torune(args[0])));
 }
 
 sl_purefn
@@ -231,10 +228,9 @@
 BUILTIN("rune-alphabetic?", rune_alphabeticp)
 {
 	argcount(nargs, 1);
-	sl_cprim *cp = ptr(args[0]);
-	if(!iscprim(args[0]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[0]))
 		type_error("rune", args[0]);
-	return isalpharune(*(Rune*)cp_data(cp)) ? sl_t : sl_nil;
+	return isalpharune(torune(args[0])) ? sl_t : sl_nil;
 }
 
 sl_purefn
@@ -241,10 +237,9 @@
 BUILTIN("rune-lower-case?", rune_lower_casep)
 {
 	argcount(nargs, 1);
-	sl_cprim *cp = ptr(args[0]);
-	if(!iscprim(args[0]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[0]))
 		type_error("rune", args[0]);
-	return islowerrune(*(Rune*)cp_data(cp)) ? sl_t : sl_nil;
+	return islowerrune(torune(args[0])) ? sl_t : sl_nil;
 }
 
 sl_purefn
@@ -251,10 +246,9 @@
 BUILTIN("rune-upper-case?", rune_upper_casep)
 {
 	argcount(nargs, 1);
-	sl_cprim *cp = ptr(args[0]);
-	if(!iscprim(args[0]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[0]))
 		type_error("rune", args[0]);
-	return isupperrune(*(Rune*)cp_data(cp)) ? sl_t : sl_nil;
+	return isupperrune(torune(args[0])) ? sl_t : sl_nil;
 }
 
 sl_purefn
@@ -261,10 +255,9 @@
 BUILTIN("rune-title-case?", rune_title_casep)
 {
 	argcount(nargs, 1);
-	sl_cprim *cp = ptr(args[0]);
-	if(!iscprim(args[0]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[0]))
 		type_error("rune", args[0]);
-	return istitlerune(*(Rune*)cp_data(cp)) ? sl_t : sl_nil;
+	return istitlerune(torune(args[0])) ? sl_t : sl_nil;
 }
 
 sl_purefn
@@ -271,10 +264,9 @@
 BUILTIN("rune-numeric?", rune_numericp)
 {
 	argcount(nargs, 1);
-	sl_cprim *cp = ptr(args[0]);
-	if(!iscprim(args[0]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[0]))
 		type_error("rune", args[0]);
-	return isdigitrune(*(Rune*)cp_data(cp)) ? sl_t : sl_nil;
+	return isdigitrune(torune(args[0])) ? sl_t : sl_nil;
 }
 
 sl_purefn
@@ -281,10 +273,9 @@
 BUILTIN("rune-whitespace?", rune_whitespacep)
 {
 	argcount(nargs, 1);
-	sl_cprim *cp = ptr(args[0]);
-	if(!iscprim(args[0]) || cp_class(cp) != sl_runetype)
+	if(!isrune(args[0]))
 		type_error("rune", args[0]);
-	return isspacerune(*(Rune*)cp_data(cp)) ? sl_t : sl_nil;
+	return isspacerune(torune(args[0])) ? sl_t : sl_nil;
 }
 
 BUILTIN("str-find", str_find)
@@ -304,9 +295,8 @@
 	usize ndbytes;
 	sl_v v = args[1];
 	char rbuf[UTFmax+1];
-	sl_cprim *cp = ptr(v);
-	if(iscprim(v) && cp_class(cp) == sl_runetype){
-		Rune r = *(Rune*)cp_data(cp);
+	if(isrune(v)){
+		Rune r = torune(v);
 		ndbytes = runetochar(rbuf, &r);
 		nd = rbuf;
 		nd[ndbytes] = 0;
@@ -358,6 +348,10 @@
 		radix = get_radix_arg(args[1]);
 	if(isfixnum(n))
 		num = numval(n);
+	else if(isubnumu(n))
+		num = ubnumuval(n);
+	else if(isubnums(n))
+		num = ubnumsval(n);
 	else if(iscprim(n)){
 		void *data = ptr(n);
 		if(cp_numtype(data) < T_FLOAT)
--- a/src/system.sl
+++ b/src/system.sl
@@ -139,11 +139,11 @@
    contains the rest of the terms."
   :doc-group doc
   (def (doc? kw)
-    (and (> (length kw) 5)
-         (= (aref kw 1) #\d)
-         (= (aref kw 2) #\o)
-         (= (aref kw 3) #\c)
-         (= (aref kw 4) #\-)))
+    (and (> (str-length kw) 5)
+         (eq? (str-rune kw 1) #\d)
+         (eq? (str-rune kw 2) #\o)
+         (eq? (str-rune kw 3) #\c)
+         (eq? (str-rune kw 4) #\-)))
   (let {[hd (car body)]
         [tl (cdr body)]}
     (cond [(and (str? hd) (not doc) tl)
@@ -162,8 +162,8 @@
       (let* {[lines (str-split doc "\n")]
              [hd (car lines)]
              [tl (cdr lines)]
-             [snd (any (λ (s) (and (> (length s) 0)
-                                   (= (aref s 0) #\space)
+             [snd (any (λ (s) (and (> (str-length s) 0)
+                                   (eq? (str-rune s 0) #\space)
                                    s))
                        tl)]
              [indent (and snd
--- a/src/types.c
+++ b/src/types.c
@@ -33,7 +33,7 @@
 	ft->numtype = NONNUMERIC;
 	if(issym(t)){
 		ft->numtype = sym_to_numtype(t);
-		assert(valid_numtype(ft->numtype));
+		assert(valid_numtype(ft->numtype) || t == sl_runesym);
 		((sl_sym*)ptr(t))->type = ft;
 	}
 	ft->size = sz;
--- a/src/vm.h
+++ b/src/vm.h
@@ -423,13 +423,13 @@
 LABEL(apply_aref):;
 	sl_v v = sp[-n];
 	for(int i = n-1; i > 0; i--){
+		sl_v e = sp[-i];
+		usize isz = tosize(e);
 		if(isarr(v)){
 			sp[-i-1] = v;
 			v = cvalue_arr_aref(sp-i-1);
 			continue;
 		}
-		sl_v e = sp[-i];
-		usize isz = tosize(e);
 		if(isvec(v)){
 			if(sl_unlikely(isz >= vec_size(v))){
 				sl.sp = sp;
--- a/test/unittest.sl
+++ b/test/unittest.sl
@@ -83,7 +83,7 @@
 
 (assert (> 9223372036854775808 9223372036854775807))
 
-(assert (fixnum? (- (aref "0" 0) #\0)))
+(assert-fail (fixnum? (- (str-rune "0" 0) #\0)))
 
 (assert (= (ash #bignum(1) -9999) 0))
 
@@ -215,6 +215,7 @@
 
 ; long argument lists
 (assert (= (apply + (iota 100000)) 4999950000))
+
 (def ones (map (λ (x) 1) (iota 80000)))
 (assert (= (eval `(if (< 2 1)
                       (+ ,@ones)
@@ -440,8 +441,8 @@
 (assert (equal? 7 (aref a 1 2)))
 (assert (equal? 5 (aref a 1 (1+ 0) 1)))
 (assert-fail (aref a 1 1 3) bounds-error)
-(assert (equal? (fixnum #\l) (aref #("hello") 0 2)))
-(assert (equal? (fixnum #\o) (aref #("hello") 0 (1+ 3))))
+(assert (equal? #\l (rune (aref #("hello") 0 2))))
+(assert (equal? #\o (rune (aref #("hello") 0 (1+ 3)))))
 (assert-fail (aref #("hello") 0 5))
 (assert-fail (aref #("hello") 1 0))
 (assert-fail (aref '(NIL) 0 0))
--- a/tools/bootstrap.sh
+++ b/tools/bootstrap.sh
@@ -1,4 +1,5 @@
 #!/bin/sh
+set -x
 test -e
 P="$(pwd)"
 F="$P/build/sl"
@@ -5,10 +6,10 @@
 test -x $F || { meson setup -Dbuildtype=debug build . && ninja -C build || exit 1; }
 test -x $F || { echo no $F found; exit 1; }
 cd src && \
-$F ../tools/gen.sl && \
+$WRAPPER $F ../tools/gen.sl && \
 cp ../boot/sl.boot ../boot/sl.boot.bak && \
-$F ../tools/mkboot0.sl builtins.sl instructions.sl system.sl compiler.sl > ../boot/sl.boot && \
+$WRAPPER $F ../tools/mkboot0.sl builtins.sl instructions.sl system.sl compiler.sl > ../boot/sl.boot && \
 ninja -C ../build && \
 cd ../boot && \
-$F ../tools/mkboot1.sl && \
+$WRAPPER $F -S 8m ../tools/mkboot1.sl && \
 ninja -C ../build || { cp "$P/boot/sl.boot.bak" "$P/boot/sl.boot"; exit 1; }
--- a/tools/gen.sl
+++ b/tools/gen.sl
@@ -6,12 +6,12 @@
 
 (def (name->cname name)
   (let {[cname (buffer)]}
-    (for 0 (1- (length name))
-      (λ (i) (let {[r (rune (aref name i))]}
+    (for 0 (1- (str-length name))
+      (λ (i) (let {[r (str-rune name i)]}
                (io-write cname
                          (cond [(rune-alphanumeric? r) (rune-upcase r)]
-                               [(= r #\?) #\P]
-                               [(= r #\_) #\_]
+                               [(eq? r #\?) #\P]
+                               [(eq? r #\_) #\_]
                                [else ""])))))
     (io->str cname)))
 
--