Skip to content

Commit

Permalink
Wasmvm memory management implementation (#53)
Browse files Browse the repository at this point in the history
* feat(memory): interfaces

* [document]: Memory Management

add a new global metadata field to save the end address of remainder
block

* [backend]: memory management

implementation of malloc/free for fast_bins

* [backend]: memory management

feat: add pseudo-random number generator for vm

* [backend]: memory management

integrate new source files into Makefile

* [backend]: memory management

update design doc
- use pointers instead of offsets for fast bins
- add a head block for sorted-block skiplist
  - update mem_init accordingly

* [backend]: memory management

implemented malloc and free for sorted_bins

* [backend]: bug fix for memory manager implementation

- Offset calculation error, as the plain pointer arithmatics are not in
  bytes;
- Refer to payload wrongly: should refer to its address, not the address
  it point to;
- Add a NULL check;
  • Loading branch information
JoeyTeng authored Dec 5, 2020
1 parent 02dbe02 commit c263e35
Show file tree
Hide file tree
Showing 6 changed files with 906 additions and 35 deletions.
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

0 comments on commit c263e35

Please sign in to comment.