risc-v中文社区

 找回密码
 立即注册
查看: 679|回复: 0

Lua源码分析 - 栈结构篇 - 数据栈和调用栈(03)

[复制链接]

347

主题

564

帖子

2237

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
2237
发表于 2022-6-20 14:40:22 | 显示全部楼层 |阅读模式
目录

一、Lua栈结构 - 栈存储方式

二、Lua栈结构 - 数据栈结构StkId

三、Lua栈结构 - 调用栈结构CallInfo

四、Lua栈结构 - 初始化和释放操作stack_init&freestack

五、Lua栈结构 - 栈扩容操作lua_checkstack

一、Lua栈结构 - 栈存储方式
Lua栈结构主要是lua_State结构上。Lua的栈结构主要由两部分组成:数据栈和调用栈

数据栈:

主要由一个StkId结构的数组组成。StkId结构为TValue,支持字符串、函数、数字等数据结构。
所有的数据都通过lapi.c文件中的lua_push*函数向栈上压入不同类型的值(数字、字符串、函数等)。
L->stack:指向栈底部。每次将一个数据压入栈上,栈指针(L->top++)都会指向到下一个结构上。
L->stacksize:栈体的大小。在栈结构初始化的时候,会分配一个BASIC_STACK_SIZE=40大小的栈结构。(LUAI_MAXSTACK 32位:15000;64位:1000000)
L->stack_last:指向栈头部,但是会留空EXTRA_STACK=5个BUF,用于元表调用或错误处理的栈操作。
L->top:栈顶指针。压入数据,都通过移动栈顶指针来实现。
调用栈:

主要由一个CallInfo的结构组成。CallInfo是一个双向链表结构。通过双向链表结构来管理每一个Lua的函数调用栈信息。
Lua一共有三种类型的函数:C语言闭包函数(例如pmain)、Lua的C函数库(例如str字符串函数)和LUA语言
每一个函数的调用,都会新生产一个CallInfo的调用栈结构,用于管理函数调用的栈指针信息。当一个函数调用结束后,会返回CallInfo链表的前一个调用栈,直到所有的调用栈结束回到L->base_ci。
调用栈最终都会指向数据栈上,通过一个个调用栈,用于管理不同的函数调用。
每次调用栈调用函数完成后,都会将函数返回的结果在栈上移动位置,将结果集逐个从ci->func位置开始填充,并调整L->top栈顶指针。这样的好处就是调用完一个函数,数据栈上只需要存储最终函数返回的结果集,不会因为复杂的函数嵌套而导致整个栈体结构迅速扩大。


  /* 调用栈:调用栈信息管理(CallInfo 为双向链表结构) */
  unsigned short nci;  /* number of items in 'ci' list - 存储一共多少个CallInfo */
  CallInfo base_ci;  /* CallInfo for first level (C calling Lua) - 调用栈的头部指针 */
  CallInfo *ci;  /* call info for current function - 当前运行函数信息 */

  /* 数据栈:栈指针地址管理  StkId = TValue 的数组 */
  StkId top;  /* first free slot in the stack - 线程栈的栈顶指针 */
  StkId stack_last;  /* last free slot in the stack  - 线程栈的最后一个位置 */
  StkId stack;  /* stack base - 栈的指针,当前执行的位置*/
  int stacksize; //栈大小
二、Lua栈结构 - 数据栈结构StkId
Lua的栈结构由一个StkId结构存储。lua中的数据分为值类型和引用类型。引用类型需要引入GC机制管理。

看一下Lua数据栈的StkId结构:

typedef TValue *StkId;  /* index to stack elements */

/*
** Union of all Lua values
*/
typedef union Value {
  GCObject *gc;    /* collectable objects 存放所有需要垃圾回收的类型的对象 */
  void *p;         /* light userdata  存放轻量用户数据类型(lightuserdata)*/
  int b;           /* booleans   */
  lua_CFunction f; /* light C functions 存放一个方法*/
  lua_Integer i;   /* integer numbers  存放int数字*/
  lua_Number n;    /* float numbers 存放float数字 */
} Value;

#define TValuefields        Value value_; int tt_

typedef struct lua_TValue {
  TValuefields;
} TValue;
三、Lua栈结构 - 调用栈结构CallInfo
Lua把调用栈和数据栈分离了,调用栈放在一个叫做 CallInfo 的结构中,以数组的形式储存在虚拟机对象里。注意:正在调用的函数一定存在于数据栈上。

ci->func:指向正在调用操作的栈底位置。(也可以认为指向调用函数的位置)
ci->top:指向调用栈的栈顶部分。默认是LUA_MINSTACK=20个栈坑,大部分情况下CallInfo也够用了。如果调用栈过大,则会抛栈溢出的错误。
previous和next是双向链表指针,用于连接各个调用栈。当执行完一个函数,通过previous回滚到上一个调用栈CI
CallInfo结构:

typedef struct CallInfo {
  StkId func;  /* 当前调用栈的调用指针处 function index in the stack    */
  StkId        top;  /* 调用栈的栈顶 top for this function */
  struct CallInfo *previous, *next;  /* dynamic call link */
  union {
    struct {  /* only for Lua functions */
      StkId base;  /* base for this function */
      const Instruction *savedpc;
    } l;
    struct {  /* only for C functions */
      lua_KFunction k;  /* continuation in case of yields */
      ptrdiff_t old_errfunc;
      lua_KContext ctx;  /* context info. in case of yields */
    } c;
  } u;
  ptrdiff_t extra;
  short nresults;  /* expected number of results from this function */
  unsigned short callstatus;
} CallInfo;
四、Lua栈结构 - 初始化和释放操作stack_init&freestack
Lua栈初始化的时候,会默认分配一个40大小的栈空间,栈尾l->stack,栈顶操作指针l->top,栈头l->stack_last

留空了EXTRA_STACK5个作为空闲buf,用于元表调用或错误处理的栈操作,也就是这些扩展槽位可以让某些操作不用考虑栈空间是否足够。

调用栈初始化的时候,ci->func指向了l->top操作指针(函数开始位置)。lstate.c文件中stack_init函数

/**
* 栈初始化(栈分为数据栈 + 调用栈)
*/
static void stack_init (lua_State *L1, lua_State *L) {
  int i; CallInfo *ci;
  /* initialize stack array */
  L1->stack = luaM_newvector(L, BASIC_STACK_SIZE, TValue); //默认40个
  L1->stacksize = BASIC_STACK_SIZE;
  for (i = 0; i < BASIC_STACK_SIZE; i++)
    setnilvalue(L1->stack + i);  /* erase new stack */
  L1->top = L1->stack;
  L1->stack_last = L1->stack + L1->stacksize - EXTRA_STACK;  //栈顶默认到35,空出5个做buf?

  /* initialize first ci */
  ci = &L1->base_ci;
  ci->next = ci->previous = NULL;
  ci->callstatus = 0;
  ci->func = L1->top;  //指向当前栈顶
  setnilvalue(L1->top++);  /* 'function' entry for this 'ci' */
  ci->top = L1->top + LUA_MINSTACK;  //指向到L1->top +20的位置
  L1->ci = ci;
}

/**
* 释放栈结构内存
*/
static void freestack (lua_State *L) {
  if (L->stack == NULL)
    return;  /* stack not completely built yet */
  L->ci = &L->base_ci;  /* free the entire 'ci' list */
  luaE_freeCI(L);
  lua_assert(L->nci == 0);
  luaM_freearray(L, L->stack, L->stacksize);  /* free stack array */
}


五、Lua栈结构 - 栈扩容操作lua_checkstack
Lua会通过lua_checkstack函数去检查栈空间的大小(lapi.c文件中)。初始化分配栈大小的时候是分配了40个StkId,栈头部留5个buf空间,所以栈头部是35个。

对于需要在循环里不断压入元素的操作,应该调用lua_checkstack用于检查每次的栈大小空间。

/**
* 检查lua_State的大小,如果栈小了,则扩容(默认栈大小:栈的默认尺寸是35)
* 说明:只会不断扩容,不会缩小
* 32/64位机器栈最大:1000000
* 16位机器栈最大:15000
*/
LUA_API int lua_checkstack (lua_State *L, int n) {
  int res;
  CallInfo *ci = L->ci;
  lua_lock(L);
  api_check(L, n >= 0, "negative 'n'");
  if (L->stack_last - L->top > n)  /* stack large enough? */
    res = 1;  /* yes; check is OK */
  else {  /* no; need to grow stack */
    int inuse = cast_int(L->top - L->stack) + EXTRA_STACK;
    if (inuse > LUAI_MAXSTACK - n)  /* can grow without overflow? */
      res = 0;  /* no */
    else  /* try to grow stack  尝试去扩容 */
      res = (luaD_rawrunprotected(L, &growstack, &n) == LUA_OK);
  }
  if (res && ci->top < L->top + n)
    ci->top = L->top + n;  /* adjust frame top */
  lua_unlock(L);
  return res;
}

/**
* 针对lua_State进行扩容
*/
static void growstack (lua_State *L, void *ud) {
  int size = *(int *)ud;
  luaD_growstack(L, size);
}

/**
* 对lua_State进行扩容
*/
void luaD_growstack (lua_State *L, int n) {
  int size = L->stacksize;
  if (size > LUAI_MAXSTACK)  /* error after extra size? */
    luaD_throw(L, LUA_ERRERR);
  else {
    int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK;
    int newsize = 2 * size;
    if (newsize > LUAI_MAXSTACK) newsize = LUAI_MAXSTACK;
    if (newsize < needed) newsize = needed;
    if (newsize > LUAI_MAXSTACK) {  /* stack overflow? */
      luaD_reallocstack(L, ERRORSTACKSIZE);
      luaG_runerror(L, "stack overflow");
    }
    else
      luaD_reallocstack(L, newsize);
  }
}

/**
* 重新分配一块statck内容,并且进行拷贝
*/
void luaD_reallocstack (lua_State *L, int newsize) {
  TValue *oldstack = L->stack;
  int lim = L->stacksize;
  lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE);
  lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK);
  luaM_reallocvector(L, L->stack, L->stacksize, newsize, TValue);
  for (; lim < newsize; lim++)
    setnilvalue(L->stack + lim); /* erase new segment */
  L->stacksize = newsize;
  L->stack_last = L->stack + newsize - EXTRA_STACK;
  correctstack(L, oldstack);
}

/**
* 拷贝到新的栈结构上
*/
static void correctstack (lua_State *L, TValue *oldstack) {
  CallInfo *ci;
  UpVal *up;
  L->top = (L->top - oldstack) + L->stack;
  for (up = L->openupval; up != NULL; up = up->u.open.next)
    up->v = (up->v - oldstack) + L->stack;
  for (ci = L->ci; ci != NULL; ci = ci->previous) {
    ci->top = (ci->top - oldstack) + L->stack;
    ci->func = (ci->func - oldstack) + L->stack;
    if (isLua(ci))
      ci->u.l.base = (ci->u.l.base - oldstack) + L->stack;
  }
}

————————————————
版权声明:本文为CSDN博主「老码农zhuli」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/initphp/article/details/97273744

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



Archiver|手机版|小黑屋|risc-v中文社区

GMT+8, 2024-5-6 11:06 , Processed in 0.014375 second(s), 17 queries .

risc-v中文社区论坛 官方网站

Copyright © 2018-2021, risc-v open source

快速回复 返回顶部 返回列表