深入剖析vscode工具函数(六)uint模块

1,224 阅读3分钟

深入剖析vscode工具函数(六)uint模块

什么是uint

在计算机科学中,uint 是一个缩写,代表“unsigned integer”,即“无符号整数”的意思。它是一种数字类型,可以用于表示只包含非负整数值的整数。

在二进制数的表示中,无符号整数与有符号整数的区别在于它们是否包含符号位。有符号整数包含一个符号位,用来表示正负数,而无符号整数则不包含符号位。因此,无符号整数可以表示比有符号整数更大的非负整数值。

在编程中,无符号整数通常用于表示与非负整数相关的数据,例如图像处理中的像素值、网络协议中的数据包大小等等。无符号整数的一个重要优点是,它可以提供更大的数值范围,以及更好的对齐和存储效率。

需要注意的是,在某些编程语言中,如 C/C++ 中,uint 是一种真正的数据类型,而在其他语言中,如 JavaScript 中,uint 可能只是一个类型定义,需要通过一些技巧模拟无符号整数的运算。

VSCode中的uint常量

在VSCode中,首先定义了一些常量:

export const enum Constants {
	/**
	 * MAX SMI (SMall Integer) as defined in v8.
	 * one bit is lost for boxing/unboxing flag.
	 * one bit is lost for sign flag.
	 * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
	 */
	MAX_SAFE_SMALL_INTEGER = 1 << 30,

	/**
	 * MIN SMI (SMall Integer) as defined in v8.
	 * one bit is lost for boxing/unboxing flag.
	 * one bit is lost for sign flag.
	 * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
	 */
	MIN_SAFE_SMALL_INTEGER = -(1 << 30),

	/**
	 * Max unsigned integer that fits on 8 bits.
	 */
	MAX_UINT_8 = 255, // 2^8 - 1

	/**
	 * Max unsigned integer that fits on 16 bits.
	 */
	MAX_UINT_16 = 65535, // 2^16 - 1

	/**
	 * Max unsigned integer that fits on 32 bits.
	 */
	MAX_UINT_32 = 4294967295, // 2^32 - 1

	UNICODE_SUPPLEMENTARY_PLANE_BEGIN = 0x010000
}
  • MAX_SAFE_SMALL_INTEGER:最大的小整数,即 V8 引擎中的 SMI(Small Integer),其值为 2302^{30}
  • MIN_SAFE_SMALL_INTEGER:最小的小整数,即 V8 引擎中的 SMI(Small Integer),其值为 230-2^{30}
  • MAX_UINT_8:8 位无符号整数的最大值,即 2812^8-1
  • MAX_UINT_16:16 位无符号整数的最大值,即 21612^{16}-1
  • MAX_UINT_32:32 位无符号整数的最大值,即 23212^{32}-1
  • UNICODE_SUPPLEMENTARY_PLANE_BEGIN:Unicode 补充平面(Supplementary Planes)的开始编码点,即 U+10000。

对于 SMI 导致的最大最小安全范围,我们展开讨论下。

关于SMI的范围

在 V8 JavaScript 引擎中,SMI(Small Integer)是一种特殊的标记值,用于表示小的整数值。

在V8中JavaScript的对象,数组,数字或者字符串都用对象表示,分配在V8堆区。这使得我们可以用一个指向对象的指针表示任何值。

许多JavaScript程序都会对整数进行计算,例如在循环中增加索引。为了避免每次整数递增时重新分配一个新的number对象,V8使用著名的指针标记技术(pointer tagging)在V8的堆指针中存储其他或替代数据。

标记位(tag bits)有双重作用:用于指示位于V8堆中对象的强/弱指针或一个小整数的信号。因此,整数能够直接存储在标记值中,而不必为其分配额外的存储空间。

V8在堆中按字对齐的地址分配对象,这使得它可以使用2(或3,取决于机器字大小)最低有效位进行标记。在32位架构中,V8使用最低有效位去区分Smis和堆对象指针。对于堆指针,它使用第二个最低有效位去区分强引用和弱引用:

                        |----- 32 bits -----|
Pointer:                |_____address_____w1|
Smi:                    |___int31_value____0|

这里的 w 用来区分强指针和弱指针。(在vscode源码注释中将这一位标记为boxing/unboxing)。

在64位架构中,V8的值看起来像这样:

            |----- 32 bits -----|----- 32 bits -----|
Pointer:    |________________address______________w1|
Smi:        |____int32_value____|0000000000000000000|

所以对于属于 SMI 标记的数字,它的最小安全范围就是30位。

关于Unicode补充平面

Unicode 补充平面是 Unicode 标准中的一个范围,用于表示超出基本多文种平面(BMP)的字符。BMP 包含了大部分常用的字符,而补充平面则用于表示一些罕见的字符,例如一些古代文字、表情符号、音乐符号等等。补充平面的代码点范围是 U+010000 至 U+10FFFF,总共包含了 1,114,112 个字符。

补充平面使用了 UTF-16 编码,其中 BMP 中的字符使用一个 16 位的代码单元表示,而补充平面中的字符则需要使用两个 16 位的代码单元来表示,这被称为“代理对”(surrogate pair)。

代理对(surrogate pair)是一种在 Unicode 表示中用于表示补充平面字符的方法。由于 Unicode 仅使用 16 位来表示每个字符,而补充平面中的字符超出了 16 位的范围,因此需要使用两个 16 位的代码单元来表示一个补充平面字符,这两个代码单元组成了一个代理对。

在 UTF-16 编码中,代理对由一个高代理项(high surrogate)和一个低代理项(low surrogate)组成,它们的码点范围分别是 U+D800 至 U+DBFF 和 U+DC00 至 U+DFFF。使用代理对可以表示 U+10000 至 U+10FFFF 的字符,这些字符都被分配到了 Unicode 的补充平面区域。

代理对的使用在处理 Unicode 字符串时非常重要。在 JavaScript 中,每个字符都被表示为一个 16 位的代码单元,如果要表示一个补充平面字符,则需要使用代理对来组合表示。例如,字符 U+1F601 (一个笑脸表情符号)的代码点为 U+1F601,它需要使用代理对 D83D 和 DE01 来表示,即 '\uD83D\uDE01'。

关于JS字符编码的历史,发展和现状,可以参考我的另一篇文章zhuanlan.zhihu.com/p/497174638

uint转换

在VSCode的实现中还有关于 uint8uint32 的两个简单的转换函数:

export function toUint8(v: number): number {
	if (v < 0) {
		return 0;
	}
	if (v > Constants.MAX_UINT_8) {
		return Constants.MAX_UINT_8;
	}
	return v | 0;
}

export function toUint32(v: number): number {
	if (v < 0) {
		return 0;
	}
	if (v > Constants.MAX_UINT_32) {
		return Constants.MAX_UINT_32;
	}
	return v | 0;
}

这两个实现不难理解,主要限定数字在这个范围内,其中最后一个位运算是与0的或运算,实际上是将浮点类型的数字强制向下取整了。

VSCode中uint的应用

其实很多地方用到了 MAX_SAFE_SMALL_INTEGER 作为边界值,比如数组下标的边界,编辑器行列的边界等等,这样做可以保证数字在安全范围内进行运算,同时也有助于提高性能,因为超过这个边界的整数在V8中要使用到堆内存了。

另外就是 uint8uint32 的转换,它们都是应用在编辑器中, uint8 用在ASCII字符的映射上, uint32 用在计算前缀和的算法上,前缀和本身就是32位的表示。

小结

本文阐述了VSCode的uint基础模块,uint是一种数字类型,可以用来表示只包含非负整数值的整数。同时我们也深入探讨了SMI的概念,这是一种特殊的标记值类型,由V8 JavaScript引擎用来表示小整数值。使用SMI对于优化执行整数计算的JavaScript程序的性能是很重要的。SMI值存储在V8堆的指针中,这使得直接将整数存储在标记值中而无需额外的存储空间成为可能。

许多VSCode函数使用MAX_SAFE_SMALL_INTEGER作为边界值,以确保计算保持在安全范围内,并且这还有助于通过减少堆内存的需求来提高性能。

另外还探讨了Unicode补充平面的概念,这是Unicode标准中用于表示基本多语言平面(BMP)之外的字符的一系列字符。这些字符包括古代文字、表情符号和音乐符号等罕见字符。补充平面使用UTF-16编码,BMP之外的字符使用代理对表示。

uint作为VSCode的一个基础模块,本身JS并没有uint类型,但是在VSCode作为编辑器场景依然需要有边界和一些对于整型数字的精准把控。从SMI边界值也可以看出VSCode工程师对于编码的健壮和性能的卓越追求。