Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wasmvm memory management implementation #53

Merged
merged 8 commits into from
Dec 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 41 additions & 31 deletions doc/deepvm memory management.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ deepvm 内存管理是基于内存池的管理,减少内存申请和释放的

- **限定单线程**环境:可以免除锁等同步机制开销
- 主要支持 IoT 环境下有限的内存大小:内存池**单池上限 4GB**(32 bit 按字节寻址),**单块上限 1GB**(30 bit 表示)
- 暂时限定 32 位寻址。
- deeplang 中受限的内存使用:**不做读写权限管理**,交由虚机与语言机制完成

#### 注意点
Expand All @@ -42,11 +43,11 @@ Allocated block 分为 head, payload 和可能存在的 padding 三个部分。
- payload 是外部可以**直接使用**的内存(裸内存),允许合法地直接读写
- padding (optional) 负责填充内存对齐情况下的剩余部分

| block head 的成员 | 作用 |
| ----------------- | --------------------------------------------------- |
| [a] allocated (1 bit) | 指示当前 block 是否被使用,此时应为 1 |
| [p] previous block allocated (1 bit) | 标识(内存地址上紧邻着的)上一个内存块是否被使用 |
| [size] block size (30 bits) | 记录 payload 内存区域的大小 |
| block head 的成员 | 作用 |
| ------------------------------------ | ------------------------------------------------ |
| [a] allocated (1 bit) | 指示当前 block 是否被使用,此时应为 1 |
| [p] previous block allocated (1 bit) | 标识(内存地址上紧邻着的)上一个内存块是否被使用 |
| [size] block size (30 bits) | 记录 head + payload 内存区域的大小 |

#### 未被使用的内存块 Free Block

Expand All @@ -62,12 +63,13 @@ Allocated block 分为 head, payload 和可能存在的 padding 三个部分。

在内存池中申请一块内存。初始化内存池的时候,内存池中只有一个 free block,由下文所述 Remainder Block 地址指针直接管理。除 head 中的两个标识位外,其内所有值均不做定义。

注意,remainder block 中的 block head 应保证有 4 bytes,payload 可以为 0 byte。即,永远存在一个 remainder block
注意,remainder block 中的 block head 应保证有 4 bytes,payload 可以至少为 0 byte。应永远存在一个 remainder block

![Remainder Block 示意图](img/deepvm-mem-block-structure-remainder-block.svg)

- allocated 应置 0
- 在 remainder block 为第一个块时,pre allocated 应置 1,其余情况下根据实际状况由其紧邻的上一个块设置。
- 实际情况下由于合并机制的存在,P 标记可以视为恒为 1。

### 内存回收加速机制 Bins

Expand All @@ -81,20 +83,20 @@ Allocated block 分为 head, payload 和可能存在的 padding 三个部分。

考虑快速分配,所有的 fast blocks 一般情况下不参与空闲内存合并操作。必要时可通过其他机制整合回收。

注意,由于单向链表仅需要指向一个方向,且每条链上的所有块大小一致,fast blocks 不包含 predecessor offset 和 footer,仅保留 head 和 successor offset。由于每个 block 都需要一个 4-byte head 和指向另一个块的 4-bytes successor 偏移值,最小的 block 至少为 8 bytes。
注意,由于单向链表仅需要指向一个方向,且每条链上的所有块大小一致,fast blocks 不包含 predecessor pointer 和 footer,仅保留 head 和 successor pointer。由于每个 block 都需要一个 4-byte head 和指向另一个块的 4-bytes successor 地址,最小的 block 至少为 8 bytes。简便起见,fast bins 使用指针(绝对地址)而不是偏移值

考虑设置以下大小的 bins[^注1]:

| Fast Bin Size (4 bytes head + n bytes payload + padding) | Comments |
| --------------------------------------------------------- | -------- |
| 8 bytes (4 + 4) | 大部分内置数值类型使用 (char / unicode char, bool, int32_t, Single precision floating number) |
| 16 bytes (4 + 12) | 部分增强内置数值类型使用 (int64_t, Double precision floating number) |
| 24 bytes (4 + 20) | 部分小型复合结构使用 (String under 20 bytes, function table with less than 5 entries) |
| 32 bytes (4 + 28) | 部分小型复合结构 |
| 40 bytes (4 + 36) | 数学库:八元组 |
| 48 bytes (4 + 44) | 其余结构。主要是保证 Sorted bins 的高效率 |
| 56 bytes (4 + 52) | 同上 |
| 64 bytes (4 + 60) | 同上,以及满足 8 bytes alignment |
| Fast Bin Size (4 bytes head + n bytes payload + padding) | Comments |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| 8 bytes (4 + 4) | 大部分内置数值类型使用 (char / unicode char, bool, int32_t, Single precision floating number) |
| 16 bytes (4 + 12) | 部分增强内置数值类型使用 (int64_t, Double precision floating number) |
| 24 bytes (4 + 20) | 部分小型复合结构使用 (String under 20 bytes, function table with less than 5 entries) |
| 32 bytes (4 + 28) | 部分小型复合结构 |
| 40 bytes (4 + 36) | 数学库:八元组 |
| 48 bytes (4 + 44) | 其余结构。主要是保证 Sorted bins 的高效率 |
| 56 bytes (4 + 52) | 同上 |
| 64 bytes (4 + 60) | 同上,以及满足 8 bytes alignment |

[^注1]: 注1:暂时只想到了这些常见应用场景

Expand Down Expand Up @@ -132,7 +134,7 @@ Sorted Bins 在分配时允许部分分配,释放时允许邻近合并,其

### 全局元信息

88 bytes。
96 bytes。

![Global Metadata 示意图](img/deepvm-mem-global-metadata.svg)

Expand All @@ -142,12 +144,16 @@ Sorted Bins 在分配时允许部分分配,释放时允许邻近合并,其

#### 第一个 Sorted Block 地址/跳表首块地址

8 bytes,也是最小的 sorted block 的地址。
8 bytes,也是最小的 sorted block 的地址。默认指向一个大小为 72 bytes 的 fake sorted block,作为头部索引。

#### 剩余块 Remainder Block 地址
#### 剩余块 Remainder Block 首地址

8 bytes。即地址上最后的 free block 的首地址,用以支持一些额外的操作。

#### 剩余块 Remainder Block 尾地址

8 bytes。即地址上最后的 free block 的尾部后的一个字节的地址,用以支持一些额外的操作。

#### Fast Bins Array

$8 \times 8 \textrm{ bytes} = 64 \textrm{ bytes}$。依序保存 fast bins 中每个 bin 的第一个元素地址,empty bin head 指向自身。
Expand All @@ -168,16 +174,20 @@ $8 \times 8 \textrm{ bytes} = 64 \textrm{ bytes}$。依序保存 fast bins 中
假设内存按字节寻址,布局方式为小端序 little-endian

1. 使用函数向系统申请 `n` bytes 大的连续内存,保存其首地址,设为 `addr`
2. 将全局可用内存空间(`addr` 处的八字节)设为 `n - 88`
3. 将跳表首块地址(`addr + 8` 处的八字节)设为 `addr + 8`
4. 将剩余块(Remainder Block)地址(`addr + 16` 处的八字节)设为 `addr + 88`
5. 将 Fast Bin Array (`addr + 24` – `addr + 87` 共 64 字节)每八字节为一组,均设为自身的地址
- 例如,`addr + 24` 起始的八字节应设为 `addr + 24`;最后一个 `addr + 80` 起始的八字节应设为 `addr + 80`
6. 将剩余块的头部(`addr + 88` – `addr + 91`)初始化
1. 设 `m` 为 remainder block 的 payload 部分的大小,即 `m = n - 92`
2. 将全局可用内存空间(`addr` 处的八字节)设为 `n - 172`
3. 将跳表首块地址(`addr + 8` 处的八字节)设为 `addr + 96`
4. 初始化跳表首块 `addr + 96 – addr + 167`
1. 将 head 设为 0,即 A/P flags 均为 0,且 block size = 0,为最小值,防止被占用
2. 将各个 offsets 设为 0
3. 将 level of indices 设为最大值 13
5. 将剩余块(Remainder Block)首地址(`addr + 16` 处的八字节)设为 `addr + 168`
6. 将剩余块(Remainder Block)尾地址(`addr + 24` 处的八字节)设为 `addr + n`
7. 将 Fast Bin Array (`addr + 32` – `addr + 95` 共 64 字节)每八字节为一组,均设为`NULL`
8. 将剩余块的头部(`addr + 168` – `addr + 171`)初始化
1. 设 `m` 为 remainder block 的 payload 部分的大小,即 `m = n - 100`
2. 将 `m` 左移 2 bits,然后将空出的两个位从低到高分别置 0, 1
3. 示例代码 (C/C++) `*((int *)(addr + 88)) = ((n - 92) << 2) & 2`
7. 初始化完成,结果如图
3. 示例代码 (C/C++) `*((int *)(addr + 96)) = ((n - 100) << 2) & 2`
9. 初始化完成,结果如图

![deep_mem_init 算法结果示意图](img/deepvm-mem-init.svg)

Expand All @@ -193,8 +203,8 @@ $8 \times 8 \textrm{ bytes} = 64 \textrm{ bytes}$。依序保存 fast bins 中

1. 计算偏移值,直接查找对应的 fast bin 下是否存在可使用的 fast block。
2. 若存在,删除对应表头元素,跳至 step 4
- 将该 fast block 的 predecessor 的 successor 偏移值更新,使其指向自身的 successor;若自身已为最后一块,则直接置为 0
3. 若不存在,依据 remainder block 地址和其 payload 大小计算出其尾地址,在尾地址处切割处对应大小的 block 返回。更新 remainder block head 中的 size 值
- 将该 fast block 的 successor 指针更新,使其指向自身的 successor;若自身已为最后一块,则直接置为 `NULL`
3. 若不存在, remainder block 尾地址处切割处对应大小的 block 返回。更新 remainder block 的尾地址
4. 更新全局可用内存空间大小。
5. 初始化将要返回的 fast block,完成后返回其 block payload 的地址。
- Block head 的 allocated 标记置 1,pre-allocated 标记置 1,block size 置为 payload 的大小(单位为**字节**)
Expand Down
10 changes: 6 additions & 4 deletions src/vm/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 指令编译器和选项
CC=gcc
CFLAGS=-Wall -std=gnu99

# 目标文件
TARGET=wasmvm
SRCS = wasm_c_api.c\
Expand All @@ -28,15 +28,17 @@ SRCS = wasm_c_api.c\
bh_list.c\
bh_log.c\
bh_common.c\
bh_assert.c
bh_assert.c\
deep_mem_alloc.c\
./include/random/xoroshiro128plus.c
INC = -I./include
OBJS = $(SRCS:.c=.o)

$(TARGET):$(OBJS)
$(CC) -o $@ $^ -lpthread -lm

clean:
rm -rf $(TARGET) $(OBJS)

%.o:%.c
$(CC) $(CFLAGS) $(INC) -o $@ -c -g $<
Loading