// File: /workspace/github_c_makefile_star2_O0/nilyang[P]redis-tdd-annotation/src/scripting.c
// Function: evalGenericCommand
void evalGenericCommand(redisClient *c, int evalsha) {
    lua_State *lua = server.lua;
    char funcname[43];
    long long numkeys;
    int delhook = 0, err;

    /* We want the same PRNG sequence at every call so that our PRNG is
     * not affected by external state. 
     *
     * 在每次执行 EVAL 时重置随机 seed ，从而保证可以生成相同的随机序列。
     */
    redisSrand48(0);

    /* We set this flag to zero to remember that so far no random command
     * was called. This way we can allow the user to call commands like
     * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command
     * is called (otherwise the replication and AOF would end with non
     * deterministic sequences).
     *
     * 两个变量：
     *
     *  - lua_random_dirty 记录脚本是否执行了随机命令
     *
     *  - lua_write_dirty 记录脚本是否进行了写入命令
     *
     * Thanks to this flag we'll raise an error every time a write command
     * is called after a random command was used. 
     *
     * 通过这两个变量，程序可以在脚本试图在执行随机命令之后执行写入时报错。
     */
    server.lua_random_dirty = 0;
    server.lua_write_dirty = 0;

    /* Get the number of arguments that are keys */
    // 获取输入键的数量
    if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK)
        return;

    // 对键的正确性做一个快速检查
    if (numkeys > (c->argc - 3)) {
        addReplyError(c,"Number of keys can't be greater than number of args");
        return;
    }

    /* We obtain the script SHA1, then check if this function is already
     * defined into the Lua state 
     *
     * 组合出函数的名字，例如 f_282297a0228f48cd3fc6a55de6316f31422f5d17
     */
    funcname[0] = 'f';
    funcname[1] = '_';
    if (!evalsha) {
        /* Hash the code if this is an EVAL call */
        // 如果执行的是 EVAL 命令，那么计算脚本的 SHA1 校验和
        sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr));
    } else {
        /* We already have the SHA if it is a EVALSHA */
        // 如果执行的是 EVALSHA 命令，直接使用传入的 SHA1 值
        int j;
        char *sha = c->argv[1]->ptr;

        /* Convert to lowercase. We don't use tolower since the function
         * managed to always show up in the profiler output consuming
         * a non trivial amount of time. */
        for (j = 0; j < 40; j++)
            funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ?
                sha[j]+('a'-'A') : sha[j];
        funcname[42] = '\0';
    }

    /* Push the pcall error handler function on the stack. */
    lua_getglobal(lua, "__redis__err__handler");

    /* Try to lookup the Lua function */
    // 根据函数名，在 Lua 环境中检查函数是否已经定义
    lua_getglobal(lua, funcname);

    if (lua_isnil(lua,-1)) {

        // 没找到脚本相应的脚本函数

        lua_pop(lua,1); /* remove the nil from the stack */
        /* Function not defined... let's define it if we have the
         * body of the function. If this is an EVALSHA call we can just
         * return an error. */

        // 脚本函数不存在，需要根据所执行的命令来做判断。。。

        // 如果执行的是 EVALSHA ，返回脚本未找到错误
        if (evalsha) {
            lua_pop(lua,1); /* remove the error handler from the stack. */
            addReply(c, shared.noscripterr);
            return;
        }

        // 如果执行的是 EVAL ，那么创建新函数，然后将代码添加到脚本字典中
        if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) {
            lua_pop(lua,1); /* remove the error handler from the stack. */
            /* The error is sent to the client by luaCreateFunction()
             * itself when it returns REDIS_ERR. */
            return;
        }
        /* Now the following is guaranteed to return non nil */
        lua_getglobal(lua, funcname);
        redisAssert(!lua_isnil(lua,-1));
    }

    /* Populate the argv and keys table accordingly to the arguments that
     * EVAL received. 
     *
     * 将用户传入的键数组和参数数组设为 Lua 环境中的 KEYS 全局变量和 ARGV 全局变量
     */
    luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
    luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);

    /* Select the right DB in the context of the Lua client */
    // 为 Lua 所属的伪客户端设置数据库
    selectDb(server.lua_client,c->db->id);
    
    /* Set a hook in order to be able to stop the script execution if it
     * is running for too much time.
     *
     * 设置一个钩子，用于在脚本运行时间过长时，停止脚本并等待用户的 SCRIPT 命令
     *
     * We set the hook only if the time limit is enabled as the hook will
     * make the Lua script execution slower. 
     *
     * 只有时间限制功能被开启时才使用钩子，因为它会拖慢脚本的运行速度。
     */
    // 调用客户端
    server.lua_caller = c;
    // 脚本开始时间
    server.lua_time_start = mstime();
    // 是否杀死脚本
    server.lua_kill = 0;
    // 只在开启了时间限制功能的时候，才使用钩子
    if (server.lua_time_limit > 0 && server.masterhost == NULL) {
        lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
        delhook = 1;
    }

    /* At this point whether this script was never seen before or if it was
     * already defined, we can call it. We have zero arguments and expect
     * a single return value. */
    // 执行脚本对应的 Lua 函数
    err = lua_pcall(lua,0,1,-2);

    /* Perform some cleanup that we need to do both on error and success. 
     *
     * 执行一些无论执行是否成功都要执行的清理工作
     */
    // 关闭超时钩子
    if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */
    // 脚本执行已超时
    if (server.lua_timedout) {
            // 清除超时 FLAG
        server.lua_timedout = 0;
        /* Restore the readable handler that was unregistered when the
         * script timeout was detected. 
         *
         * 将超时钩子里删除的读事件重新加上，
         * 使得服务器可以继续接受当前客户端发来的命令请求
         */
        aeCreateFileEvent(server.el,c->fd,AE_READABLE,
                          readQueryFromClient,c);
    }

    // 清空调用者
    server.lua_caller = NULL;

    // 因为客户端的当前数据库在脚本执行之后可能已经改变
    // 所以这里需要更新客户端
    selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */

    /* Call the Lua garbage collector from time to time to avoid a
     * full cycle performed by Lua, which adds too latency.
     *
     * The call is performed every LUA_GC_CYCLE_PERIOD executed commands
     * (and for LUA_GC_CYCLE_PERIOD collection steps) because calling it
     * for every command uses too much CPU. */
    #define LUA_GC_CYCLE_PERIOD 50
    {
        static long gc_count = 0;

        gc_count++;
        if (gc_count == LUA_GC_CYCLE_PERIOD) {
            lua_gc(lua,LUA_GCSTEP,LUA_GC_CYCLE_PERIOD);
            gc_count = 0;
        }
    }

    // 检查脚本运行是否出错
    if (err) {
        // 出错
        addReplyErrorFormat(c,"Error running script (call to %s): %s\n",
            funcname, lua_tostring(lua,-1));
        lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */
    } else {
        /* On success convert the Lua return value into Redis protocol, and
         * send it to * the client.
         * 执行成功，将结果转换回 Redis 回复，并返回给客户端 */
        luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */
        lua_pop(lua,1); /* Remove the error handler. */
    }

    /* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless
     * we are sure that the script was already in the context of all the
     * attached slaves *and* the current AOF file if enabled.
     *
     * 除非程序确认脚本已经存在于所有 slave 和 AOF 文件的上下文中，
     * 否则在传播脚本到 slave 和 AOF 文件时，
     * 必须将 EVALSHA 当作 EVAL 来传播。
     *
     * To do so we use a cache of SHA1s of scripts that we already propagated
     * as full EVAL, that's called the Replication Script Cache.
     *
     * 为此，服务器使用一个字典来作为已传播脚本的缓存，
     * 这个缓存称之为 Replication Script Cache ，
     * 缓存字典的键为 SHA1 ，值为 NULL （一个集合）。
     *
     * For repliation, everytime a new slave attaches to the master, we need to
     * flush our cache of scripts that can be replicated as EVALSHA, while
     * for AOF we need to do so every time we rewrite the AOF file. 
     *
     * 每次有一个新的 slave 连上主服务器时，
     * 或者每次进行 AOF 重写时，程序清空缓存字典
     */
    if (evalsha) {
        // replicationScriptCacheExists 如果返回 1 
        // 那么表示所有 slave 和 AOF 文件中都知道这个脚本
        // 如果返回 0 ，那么表示这个脚本未被传播到所有 slave 和 AOF 文件中
        if (!replicationScriptCacheExists(c->argv[1]->ptr)) {
            /* This script is not in our script cache, replicate it as
             * EVAL, then add it into the script cache, as from now on
             * slaves and AOF know about it. */
            // 将这个脚本加入脚本缓存，并且将以 EVAL 命令的方式传播它
            // 从现在开始， slaves 和 AOF 文件都知道它

            // 取出脚本 body
            robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr);

            // 将脚本的 SHA1 值加入到缓存
            replicationScriptCacheAdd(c->argv[1]->ptr);
            redisAssertWithInfo(c,NULL,script != NULL);
            // 将 EVALSHA 命令改写为 EVAL
            rewriteClientCommandArgument(c,0,
                resetRefCount(createStringObject("EVAL",4)));
            rewriteClientCommandArgument(c,1,script);
            forceCommandPropagation(c,REDIS_PROPAGATE_REPL|REDIS_PROPAGATE_AOF);
        }
    }
}

// Assembly:
pushq %rbp
movq %rsp, %rbp
subq $0xb0, %rsp
movq %rdi, -0x8(%rbp)
movl %esi, -0xc(%rbp)
leaq 0x9ea8f(%rip), %rax     # 0x1138f8
movq 0x7e0(%rax), %rax
movq %rax, -0x18(%rbp)
movl $0x0, -0x5c(%rbp)
xorl %edi, %edi
callq 0x763b0
leaq 0x9ea6f(%rip), %rax     # 0x1138f8
movl $0x0, 0x814(%rax)
leaq 0x9ea5e(%rip), %rax     # 0x1138f8
movl $0x0, 0x810(%rax)
movq -0x8(%rbp), %rdi
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rax
movq 0x10(%rax), %rsi
leaq -0x58(%rbp), %rdx
xorl %eax, %eax
movl %eax, %ecx
callq 0x3b8e0
cmpl $0x0, %eax
je 0x74ecb
jmp 0x7541e
movq -0x58(%rbp), %rax
movq -0x8(%rbp), %rcx
movl 0x30(%rcx), %ecx
subl $0x3, %ecx
movslq %ecx, %rcx
cmpq %rcx, %rax
jle 0x74ef6
movq -0x8(%rbp), %rdi
leaq 0x72f0d(%rip), %rsi     # 0xe7df9
callq 0x35d40
jmp 0x7541e
movb $0x66, -0x50(%rbp)
movb $0x5f, -0x4f(%rbp)
cmpl $0x0, -0xc(%rbp)
jne 0x74f54
leaq -0x50(%rbp), %rax
addq $0x2, %rax
movq %rax, -0x88(%rbp)
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rax
movq 0x8(%rax), %rax
movq 0x8(%rax), %rax
movq %rax, -0x80(%rbp)
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rax
movq 0x8(%rax), %rax
movq 0x8(%rax), %rdi
callq 0x72b70
movq -0x88(%rbp), %rdi
movq -0x80(%rbp), %rsi
movq %rax, %rdx
callq 0x73930
jmp 0x74fe3
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rax
movq 0x8(%rax), %rax
movq 0x8(%rax), %rax
movq %rax, -0x70(%rbp)
movl $0x0, -0x64(%rbp)
cmpl $0x28, -0x64(%rbp)
jge 0x74fdf
movq -0x70(%rbp), %rax
movslq -0x64(%rbp), %rcx
movsbl (%rax,%rcx), %eax
cmpl $0x41, %eax
jl 0x74fae
movq -0x70(%rbp), %rax
movslq -0x64(%rbp), %rcx
movsbl (%rax,%rcx), %eax
cmpl $0x5a, %eax
jg 0x74fae
movq -0x70(%rbp), %rax
movslq -0x64(%rbp), %rcx
movsbl (%rax,%rcx), %eax
addl $0x20, %eax
movl %eax, -0x8c(%rbp)
jmp 0x74fc0
movq -0x70(%rbp), %rax
movslq -0x64(%rbp), %rcx
movsbl (%rax,%rcx), %eax
movl %eax, -0x8c(%rbp)
movl -0x8c(%rbp), %eax
movb %al, %cl
movl -0x64(%rbp), %eax
addl $0x2, %eax
cltq
movb %cl, -0x50(%rbp,%rax)
movl -0x64(%rbp), %eax
addl $0x1, %eax
movl %eax, -0x64(%rbp)
jmp 0x74f6f
movb $0x0, -0x26(%rbp)
movq -0x18(%rbp), %rdi
movl $0xffffd8ee, %esi       # imm = 0xFFFFD8EE
leaq 0x72e3a(%rip), %rdx     # 0xe7e2d
callq 0x8c9d0
movq -0x18(%rbp), %rdi
leaq -0x50(%rbp), %rdx
movl $0xffffd8ee, %esi       # imm = 0xFFFFD8EE
callq 0x8c9d0
movq -0x18(%rbp), %rdi
movl $0xffffffff, %esi       # imm = 0xFFFFFFFF
callq 0x8bc20
cmpl $0x0, %eax
jne 0x750df
movq -0x18(%rbp), %rdi
movl $0xfffffffe, %esi       # imm = 0xFFFFFFFE
callq 0x8b6f0
cmpl $0x0, -0xc(%rbp)
je 0x7505f
movq -0x18(%rbp), %rdi
movl $0xfffffffe, %esi       # imm = 0xFFFFFFFE
callq 0x8b6f0
movq -0x8(%rbp), %rdi
leaq 0x9f102(%rip), %rax     # 0x114150
movq 0x98(%rax), %rsi
callq 0x35a70
jmp 0x7541e
movq -0x8(%rbp), %rdi
movq -0x18(%rbp), %rsi
leaq -0x50(%rbp), %rdx
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rax
movq 0x8(%rax), %rcx
callq 0x74c30
cmpl $-0x1, %eax
jne 0x75094
movq -0x18(%rbp), %rdi
movl $0xfffffffe, %esi       # imm = 0xFFFFFFFE
callq 0x8b6f0
jmp 0x7541e
movq -0x18(%rbp), %rdi
leaq -0x50(%rbp), %rdx
movl $0xffffd8ee, %esi       # imm = 0xFFFFD8EE
callq 0x8c9d0
movq -0x18(%rbp), %rdi
movl $0xffffffff, %esi       # imm = 0xFFFFFFFF
callq 0x8bc20
cmpl $0x0, %eax
je 0x750bb
jmp 0x750dd
leaq 0x72d81(%rip), %rdi     # 0xe7e43
leaq 0x72d24(%rip), %rsi     # 0xe7ded
movl $0x4bf, %edx            # imm = 0x4BF
callq 0x63ca0
movl $0x1, %edi
callq 0x1f3b0
jmp 0x750df
movq -0x18(%rbp), %rdi
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rdx
addq $0x18, %rdx
movq -0x58(%rbp), %rax
movl %eax, %ecx
leaq 0x72d5a(%rip), %rsi     # 0xe7e56
callq 0x74b80
movq -0x18(%rbp), %rdi
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rdx
addq $0x18, %rdx
movq -0x58(%rbp), %rax
shlq $0x3, %rax
addq %rax, %rdx
movq -0x8(%rbp), %rax
movl 0x30(%rax), %eax
subl $0x3, %eax
cltq
subq -0x58(%rbp), %rax
movl %eax, %ecx
leaq 0x72d26(%rip), %rsi     # 0xe7e5b
callq 0x74b80
leaq 0x9e7b7(%rip), %rax     # 0x1138f8
movq 0x7e8(%rax), %rdi
movq -0x8(%rbp), %rax
movq 0x8(%rax), %rax
movl 0x30(%rax), %esi
callq 0x3c7d0
movq -0x8(%rbp), %rcx
leaq 0x9e795(%rip), %rax     # 0x1138f8
movq %rcx, 0x7f0(%rax)
callq 0x252e0
movq %rax, %rcx
leaq 0x9e77f(%rip), %rax     # 0x1138f8
movq %rcx, 0x808(%rax)
leaq 0x9e771(%rip), %rax     # 0x1138f8
movl $0x0, 0x81c(%rax)
leaq 0x9e760(%rip), %rax     # 0x1138f8
cmpq $0x0, 0x800(%rax)
jle 0x751d4
leaq 0x9e74f(%rip), %rax     # 0x1138f8
cmpq $0x0, 0x650(%rax)
jne 0x751d4
movq -0x18(%rbp), %rdi
leaq -0x154e(%rip), %rsi     # 0x73c70
movl $0x8, %edx
movl $0x186a0, %ecx          # imm = 0x186A0
callq 0x8dcf0
movl $0x1, -0x5c(%rbp)
movq -0x18(%rbp), %rdi
xorl %esi, %esi
movl $0x1, %edx
movl $0xfffffffe, %ecx       # imm = 0xFFFFFFFE
callq 0x8d340
movl %eax, -0x60(%rbp)
cmpl $0x0, -0x5c(%rbp)
je 0x75206
movq -0x18(%rbp), %rdi
leaq -0x158d(%rip), %rsi     # 0x73c70
xorl %ecx, %ecx
movl %ecx, %edx
callq 0x8dcf0
leaq 0x9e6eb(%rip), %rax     # 0x1138f8
cmpl $0x0, 0x818(%rax)
je 0x7524d
leaq 0x9e6db(%rip), %rax     # 0x1138f8
movl $0x0, 0x818(%rax)
leaq 0x9e6ca(%rip), %rax     # 0x1138f8
movq 0x28(%rax), %rdi
movq -0x8(%rbp), %rax
movl (%rax), %esi
movq -0x8(%rbp), %r8
movl $0x1, %edx
leaq -0x40598(%rip), %rcx    # 0x34cb0
callq 0x20a80
leaq 0x9e6a4(%rip), %rax     # 0x1138f8
movq $0x0, 0x7f0(%rax)
movq -0x8(%rbp), %rdi
leaq 0x9e68e(%rip), %rax     # 0x1138f8
movq 0x7e8(%rax), %rax
movq 0x8(%rax), %rax
movl 0x30(%rax), %esi
callq 0x3c7d0
movq 0xb2eac(%rip), %rax     # 0x128130
addq $0x1, %rax
movq %rax, 0xb2ea1(%rip)     # 0x128130
cmpq $0x32, 0xb2e99(%rip)    # 0x128130
jne 0x752b7
movq -0x18(%rbp), %rdi
movl $0x5, %esi
movl $0x32, %edx
callq 0x8d680
movq $0x0, 0xb2e79(%rip) # 0x128130
cmpl $0x0, -0x60(%rbp)
je 0x75314
movq -0x8(%rbp), %rax
movq %rax, -0xa0(%rbp)
leaq -0x50(%rbp), %rax
movq %rax, -0x98(%rbp)
movq -0x18(%rbp), %rdi
movl $0xffffffff, %esi       # imm = 0xFFFFFFFF
xorl %eax, %eax
movl %eax, %edx
callq 0x8c0e0
movq -0xa0(%rbp), %rdi
movq -0x98(%rbp), %rdx
movq %rax, %rcx
leaq 0x72b63(%rip), %rsi     # 0xe7e60
movb $0x0, %al
callq 0x35d80
movq -0x18(%rbp), %rdi
movl $0xfffffffd, %esi       # imm = 0xFFFFFFFD
callq 0x8b6f0
jmp 0x7532f
movq -0x8(%rbp), %rdi
movq -0x18(%rbp), %rsi
callq 0x74820
movq -0x18(%rbp), %rdi
movl $0xfffffffe, %esi       # imm = 0xFFFFFFFE
callq 0x8b6f0
cmpl $0x0, -0xc(%rbp)
je 0x7541e
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rax
movq 0x8(%rax), %rax
movq 0x8(%rax), %rdi
callq 0x42610
cmpl $0x0, %eax
jne 0x7541c
leaq 0x9e59a(%rip), %rax     # 0x1138f8
movq 0x7f8(%rax), %rdi
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rax
movq 0x8(%rax), %rax
movq 0x8(%rax), %rsi
callq 0x24050
movq %rax, -0x78(%rbp)
movq -0x8(%rbp), %rax
movq 0x38(%rax), %rax
movq 0x8(%rax), %rax
movq 0x8(%rax), %rdi
callq 0x424f0
cmpq $0x0, -0x78(%rbp)
je 0x7539c
jmp 0x753c7
movq -0x8(%rbp), %rdi
xorl %eax, %eax
movl %eax, %esi
leaq 0x72adc(%rip), %rdx     # 0xe7e87
leaq 0x72a3b(%rip), %rcx     # 0xe7ded
movl $0x54a, %r8d            # imm = 0x54A
callq 0x648b0
movl $0x1, %edi
callq 0x1f3b0
movq -0x8(%rbp), %rax
movq %rax, -0xa8(%rbp)
leaq 0x72abd(%rip), %rdi     # 0xe7e96
movl $0x4, %esi
callq 0x3a2e0
movq %rax, %rdi
callq 0x3ab90
movq -0xa8(%rbp), %rdi
movq %rax, %rdx
xorl %esi, %esi
callq 0x38a50
movq -0x8(%rbp), %rdi
movq -0x78(%rbp), %rdx
movl $0x1, %esi
callq 0x38a50
movq -0x8(%rbp), %rdi
movl $0x3, %esi
callq 0x28850
jmp 0x7541e
addq $0xb0, %rsp
popq %rbp
retq
nopw (%rax,%rax)