feat(ir2mcode): 实现AMD64代码生成器支持控制流和函数调用

- 实现了条件分支、无条件跳转和函数调用的机器码生成
- 添加了跳转目标地址回填机制,处理条件分支和跳转指令的偏移量
- 改进了寄存器分配逻辑,支持函数调用返回值的处理
- 重构了位置解析函数,从返回指针改为传入引用参数

fix(ast2ir): 移除无用的注释代码

- 删除了关于一元操作符映射的注释代码

test: 更新测试框架和测试用例

- 修改测试框架以支持新的可执行文件输出格式
- 添加了条件分支、循环和函数调用的测试用例
- 使用TOML配置文件管理期望的返回值
- 替换标准库头文件为自定义头文件以减少依赖
This commit is contained in:
zzy
2026-03-21 14:38:30 +08:00
parent 35a704a1cb
commit 097dbdcc2a
8 changed files with 340 additions and 101 deletions

View File

@@ -234,7 +234,6 @@ scc_ir_node_ref_t scc_ast2ir_expr(scc_ast2ir_ctx_t *ctx, scc_ast_expr_t *expr,
// SCC_AST_OP_PREFIX_DECREMENT, // -- (前缀)
// SCC_AST_OP_POSTFIX_INCREMENT, // ++ (后缀)
// SCC_AST_OP_POSTFIX_DECREMENT, // -- (后缀)
// 映射一元操作符
switch (expr->unary.op) {
case SCC_AST_OP_UNARY_MINUS:
// 负号

View File

@@ -3,12 +3,14 @@
#include <reg_alloc.h>
#include <scc_ir2mcode.h>
static scc_reg_loc_t *parse_location(scc_ir2mcode_ctx_t *ctx,
scc_ir_bblock_ref_t node_ref) {
static void parse_location(scc_ir2mcode_ctx_t *ctx, scc_reg_loc_t *loc,
scc_ir_bblock_ref_t node_ref) {
Assert(ctx != null && loc != null);
scc_ir_node_t *node = scc_ir_ctx_get_node(ctx->ir_ctx, node_ref);
if (node == null) {
LOG_FATAL("invalid node ref");
return null;
UNREACHABLE();
return;
}
usize idx = 0;
switch (node->tag) {
@@ -16,11 +18,11 @@ static scc_reg_loc_t *parse_location(scc_ir2mcode_ctx_t *ctx,
scc_ir_type_t *type = scc_ir_ctx_get_type(ctx->ir_ctx, node->type);
Assert(type != 0);
Assert(type->tag == SCC_IR_TYPE_U32 || type->tag == SCC_IR_TYPE_I32);
scc_reg_loc_t loc = {.kind = SCC_REG_KIND_IMM,
.idx = (usize)node->data.const_int.int32};
scc_vec_push(ctx->reg_alloc.reg_loc_vec, loc);
idx = scc_vec_size(ctx->reg_alloc.reg_loc_vec);
break;
*loc = (scc_reg_loc_t){
.kind = SCC_REG_KIND_IMM,
.idx = (usize)node->data.const_int.int32,
};
return;
case SCC_IR_NODE_CONST_UINT:
case SCC_IR_NODE_CONST_FLOAT:
TODO();
@@ -32,7 +34,7 @@ static scc_reg_loc_t *parse_location(scc_ir2mcode_ctx_t *ctx,
}
Assert(idx > 0 && idx <= scc_vec_size(ctx->reg_alloc.reg_loc_vec));
return &scc_vec_at(ctx->reg_alloc.reg_loc_vec, idx - 1);
*loc = scc_vec_at(ctx->reg_alloc.reg_loc_vec, idx - 1);
}
static void load_value_to_reg(scc_mcode_t *mcode, scc_reg_loc_t *loc, int reg) {
@@ -77,8 +79,15 @@ static void store_value_from_reg(scc_mcode_t *mcode, scc_reg_loc_t *loc,
}
}
// 临时存储待修补条目
typedef struct {
usize pos;
usize target_bb_ref;
} patch_t;
typedef SCC_VEC(patch_t) patch_vec_t;
static void parse_node(scc_ir2mcode_ctx_t *ctx, scc_ir_bblock_ref_t node_ref,
int idx) {
patch_vec_t *patches) {
scc_ir_node_t *node = scc_ir_ctx_get_node(ctx->ir_ctx, node_ref);
if (node == null) {
LOG_ERROR("invalid node ref");
@@ -97,32 +106,39 @@ static void parse_node(scc_ir2mcode_ctx_t *ctx, scc_ir_bblock_ref_t node_ref,
case SCC_IR_NODE_LOAD: ///< 加载数据
{
// node->data.load.target
scc_reg_loc_t *from = parse_location(ctx, node->data.load.target);
scc_reg_loc_t *to = parse_location(ctx, node_ref);
load_value_to_reg(&ctx->mcode, from, SCC_AMD64_RAX);
store_value_from_reg(&ctx->mcode, to, SCC_AMD64_RAX);
scc_reg_loc_t from;
scc_reg_loc_t to;
parse_location(ctx, &from, node->data.load.target);
parse_location(ctx, &to, node_ref);
load_value_to_reg(&ctx->mcode, &from, SCC_AMD64_RAX);
store_value_from_reg(&ctx->mcode, &to, SCC_AMD64_RAX);
break;
}
case SCC_IR_NODE_STORE: ///< 存储数据
{
scc_reg_loc_t *from = parse_location(ctx, node->data.store.value);
scc_reg_loc_t *to = parse_location(ctx, node->data.store.target);
load_value_to_reg(&ctx->mcode, from, SCC_AMD64_RAX);
store_value_from_reg(&ctx->mcode, to, SCC_AMD64_RAX);
scc_reg_loc_t from;
scc_reg_loc_t to;
parse_location(ctx, &from, node->data.store.value);
parse_location(ctx, &to, node->data.store.target);
load_value_to_reg(&ctx->mcode, &from, SCC_AMD64_RAX);
store_value_from_reg(&ctx->mcode, &to, SCC_AMD64_RAX);
break;
}
case SCC_IR_NODE_GET_PTR: ///< 获取指针
case SCC_IR_NODE_GET_ELEM_PTR: ///< 获取元素指针(used by array)
TODO();
case SCC_IR_NODE_OP: ///< 二元运算
scc_reg_loc_t *loc_lhs = parse_location(ctx, node->data.op.lhs);
scc_reg_loc_t *loc_rhs = parse_location(ctx, node->data.op.rhs);
scc_reg_loc_t *loc_res = parse_location(ctx, node_ref);
scc_reg_loc_t loc_lhs;
parse_location(ctx, &loc_lhs, node->data.op.lhs);
scc_reg_loc_t loc_rhs;
parse_location(ctx, &loc_rhs, node->data.op.rhs);
scc_reg_loc_t loc_res;
parse_location(ctx, &loc_res, node_ref);
// 将左操作数加载到 RAX临时结果寄存器
load_value_to_reg(&ctx->mcode, loc_lhs, SCC_AMD64_RAX);
load_value_to_reg(&ctx->mcode, &loc_lhs, SCC_AMD64_RAX);
// 将右操作数加载到 RCX
load_value_to_reg(&ctx->mcode, loc_rhs, SCC_AMD64_RCX);
load_value_to_reg(&ctx->mcode, &loc_rhs, SCC_AMD64_RCX);
switch (node->data.op.op) {
case SCC_IR_OP_ADD:
scc_mcode_amd64_add_r64_r64(&ctx->mcode, SCC_AMD64_RAX,
@@ -135,59 +151,195 @@ static void parse_node(scc_ir2mcode_ctx_t *ctx, scc_ir_bblock_ref_t node_ref,
case SCC_IR_OP_MUL:
scc_mcode_amd64_mul_r64(&ctx->mcode, SCC_AMD64_RCX);
break;
// [SCC_IR_OP_EMPTY] = "empty", [SCC_IR_OP_NEQ] = "!=",
// [SCC_IR_OP_EQ] = "==", [SCC_IR_OP_GT] = ">",
// [SCC_IR_OP_LT] = "<", [SCC_IR_OP_GE] = ">=",
// [SCC_IR_OP_LE] = "<=", [SCC_IR_OP_ADD] = "+",
// [SCC_IR_OP_SUB] = "-", [SCC_IR_OP_MUL] = "*",
// [SCC_IR_OP_DIV] = "/", [SCC_IR_OP_MOD] = "%",
// [SCC_IR_OP_AND] = "&", [SCC_IR_OP_OR] = "|",
// [SCC_IR_OP_XOR] = "^", [SCC_IR_OP_NOT] = "~",
// [SCC_IR_OP_SHL] = "<<", [SCC_IR_OP_SHR] = ">>",
// [SCC_IR_OP_SAR] = ">>a", // Arithmetic shift right
case SCC_IR_OP_NEQ:
scc_mcode_amd64_cmp_r64_r64(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RCX);
scc_mcode_amd64_setcc_r8(&ctx->mcode, SCC_AMD64_COND_NE,
SCC_AMD64_RAX);
scc_mcode_amd64_movzx_r64_r8(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RAX);
break;
case SCC_IR_OP_EQ:
scc_mcode_amd64_cmp_r64_r64(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RCX);
scc_mcode_amd64_setcc_r8(&ctx->mcode, SCC_AMD64_COND_E,
SCC_AMD64_RAX);
scc_mcode_amd64_movzx_r64_r8(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RAX);
break;
case SCC_IR_OP_GT:
scc_mcode_amd64_cmp_r64_r64(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RCX);
scc_mcode_amd64_setcc_r8(&ctx->mcode, SCC_AMD64_COND_G,
SCC_AMD64_RAX);
scc_mcode_amd64_movzx_r64_r8(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RAX);
break;
case SCC_IR_OP_LT:
scc_mcode_amd64_cmp_r64_r64(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RCX);
scc_mcode_amd64_setcc_r8(&ctx->mcode, SCC_AMD64_COND_L,
SCC_AMD64_RAX);
scc_mcode_amd64_movzx_r64_r8(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RAX);
break;
case SCC_IR_OP_GE:
scc_mcode_amd64_cmp_r64_r64(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RCX);
scc_mcode_amd64_setcc_r8(&ctx->mcode, SCC_AMD64_COND_GE,
SCC_AMD64_RAX);
scc_mcode_amd64_movzx_r64_r8(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RAX);
break;
case SCC_IR_OP_LE:
scc_mcode_amd64_cmp_r64_r64(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RCX);
scc_mcode_amd64_setcc_r8(&ctx->mcode, SCC_AMD64_COND_LE,
SCC_AMD64_RAX);
scc_mcode_amd64_movzx_r64_r8(&ctx->mcode, SCC_AMD64_RAX,
SCC_AMD64_RAX);
break;
default:
LOG_FATAL("unknown op: %d", node->data.op.op);
break;
}
// 将 RAX 中的结果存储到 res 位置
store_value_from_reg(&ctx->mcode, loc_res, SCC_AMD64_RAX);
// [SCC_IR_OP_EMPTY] = "empty", [SCC_IR_OP_NEQ] = "!=",
// [SCC_IR_OP_EQ] = "==", [SCC_IR_OP_GT] = ">",
// [SCC_IR_OP_LT] = "<", [SCC_IR_OP_GE] = ">=",
// [SCC_IR_OP_LE] = "<=", [SCC_IR_OP_ADD] = "+",
// [SCC_IR_OP_SUB] = "-", [SCC_IR_OP_MUL] = "*",
// [SCC_IR_OP_DIV] = "/", [SCC_IR_OP_MOD] = "%",
// [SCC_IR_OP_AND] = "&", [SCC_IR_OP_OR] = "|",
// [SCC_IR_OP_XOR] = "^", [SCC_IR_OP_NOT] = "~",
// [SCC_IR_OP_SHL] = "<<", [SCC_IR_OP_SHR] = ">>",
// [SCC_IR_OP_SAR] = ">>a", // Arithmetic shift right
store_value_from_reg(&ctx->mcode, &loc_res, SCC_AMD64_RAX);
break;
case SCC_IR_NODE_BRANCH: ///< 有条件分支
case SCC_IR_NODE_JUMP: ///< 无条件跳转
case SCC_IR_NODE_CALL: ///< 调用函数
LOG_FATAL("Unsupported node type: %d", node->tag);
///< 有条件分支
case SCC_IR_NODE_BRANCH: {
scc_reg_loc_t loc;
parse_location(ctx, &loc, node->data.branch.cond);
// (void)loc;
load_value_to_reg(&ctx->mcode, &loc, SCC_AMD64_RAX);
scc_mcode_amd64_cmp_r64_imm32(&ctx->mcode, SCC_AMD64_RAX, 0);
scc_mcode_amd64_jcc_rel32(&ctx->mcode, SCC_AMD64_COND_NE, 0);
patch_t patch_true = {.pos = scc_vec_size(ctx->mcode.mcode),
.target_bb_ref =
(usize)node->data.branch.true_bblock};
scc_vec_push(*patches, patch_true);
scc_mcode_amd64_jmp_rel32(&ctx->mcode, 0);
patch_t patch_false = {.pos = scc_vec_size(ctx->mcode.mcode),
.target_bb_ref =
(usize)node->data.branch.false_bblock};
scc_vec_push(*patches, patch_false);
break;
case SCC_IR_NODE_RET: ///< 函数返回
}
///< 无条件跳转
case SCC_IR_NODE_JUMP: {
scc_mcode_amd64_jmp_rel32(&ctx->mcode, 0);
usize pos = scc_vec_size(ctx->mcode.mcode);
patch_t patch = {.pos = pos,
.target_bb_ref = (usize)node->data.jump.target_bblock};
scc_vec_push(*patches, patch);
break;
}
///< 调用函数
case SCC_IR_NODE_CALL: {
scc_reg_loc_t loc;
/*
ABI
RAX 不稳定的 返回值寄存器
RCX 不稳定的 第一个整型自变量
RDX 不稳定的 第二个整型自变量
R8 不稳定的 第三个整型自变量
R9 不稳定的 第四个整型自变量
*/
scc_vec_foreach(node->data.call.args, i) {
parse_location(ctx, &loc, node->data.branch.cond);
if (i == 0) {
load_value_to_reg(&ctx->mcode, &loc, SCC_AMD64_RCX);
} else if (i == 1) {
load_value_to_reg(&ctx->mcode, &loc, SCC_AMD64_RDX);
} else if (i == 2) {
load_value_to_reg(&ctx->mcode, &loc, SCC_AMD64_R8);
} else if (i == 3) {
load_value_to_reg(&ctx->mcode, &loc, SCC_AMD64_R9);
} else {
LOG_FATAL("not support more than 4 args");
}
// scc_mcode_amd64_push_r64();
}
scc_ir_func_t *func =
scc_ir_ctx_get_func(ctx->ir_ctx, node->data.call.callee);
if (!func) {
LOG_ERROR("invalid function reference");
return;
}
scc_mcode_amd64_call_rel32(&ctx->mcode, 0);
// 处理返回值
scc_ir_type_t *ret_type = scc_ir_ctx_get_type(ctx->ir_ctx, node->type);
if (ret_type && ret_type->tag != SCC_IR_TYPE_VOID) {
parse_location(ctx, &loc, node_ref);
store_value_from_reg(&ctx->mcode, &loc, SCC_AMD64_RAX);
}
break;
}
///< 函数返回
case SCC_IR_NODE_RET: {
if (node->data.ret.ret_val) {
scc_reg_loc_t *loc = parse_location(ctx, node->data.ret.ret_val);
load_value_to_reg(&ctx->mcode, loc, SCC_AMD64_RAX);
scc_reg_loc_t loc;
parse_location(ctx, &loc, node->data.ret.ret_val);
load_value_to_reg(&ctx->mcode, &loc, SCC_AMD64_RAX);
}
scc_mcode_amd64_mov_r64_r64(&ctx->mcode, SCC_AMD64_RSP, SCC_AMD64_RBP);
scc_mcode_amd64_pop_r64(&ctx->mcode, SCC_AMD64_RBP);
scc_mcode_amd64_ret(&ctx->mcode);
break;
}
default:
UNREACHABLE();
break;
}
}
static void parse_bblock(scc_ir2mcode_ctx_t *ctx, scc_ir_bblock_t *bblock) {
static void parse_bblock(scc_ir2mcode_ctx_t *ctx, scc_ir_bblock_t *bblock,
patch_vec_t *patches) {
// 打印基本块中的每条指令
for (usize i = 0; i < scc_vec_size(bblock->instrs); i++) {
scc_ir_node_ref_t node_ref = scc_vec_at(bblock->instrs, i);
parse_node(ctx, node_ref, i);
parse_node(ctx, node_ref, patches);
}
}
static void parse_function(scc_ir2mcode_ctx_t *ctx, scc_ir_func_t *func) {
scc_hashtable_t bblock2offset;
static u32 hash_func(const void *key) { return (u32)(usize)key; }
static int equal_func(const void *key1, const void *key2) {
return (usize)key1 - (usize)key2;
}
static void parse_function(scc_ir2mcode_ctx_t *ctx, scc_ir_func_t *func) {
ctx->noderef2regloc = scc_reg_alloc(&ctx->reg_alloc, func);
// 对齐到 16 字节
usize stack_size = (ctx->reg_alloc.alloc_stack_size + 15) & ~15;
usize bblock_cnt = scc_vec_size(func->bblocks);
usize *bblock_offsets = scc_calloc(bblock_cnt, sizeof(usize));
// 建立 bblock_ref -> id 的映射
scc_hashtable_t ref2id;
scc_hashtable_init(&ref2id, hash_func, equal_func);
for (usize i = 0; i < bblock_cnt; i++) {
scc_ir_bblock_ref_t ref = scc_vec_at(func->bblocks, i);
scc_hashtable_set(&ref2id, (void *)(usize)ref, (void *)i);
}
patch_vec_t patches;
scc_vec_init(patches);
scc_mcode_amd64_push_r64(&ctx->mcode, SCC_AMD64_RBP);
scc_mcode_amd64_mov_r64_r64(&ctx->mcode, SCC_AMD64_RBP, SCC_AMD64_RSP);
scc_mcode_amd64_sub_rsp_imm32(&ctx->mcode, stack_size);
@@ -201,8 +353,30 @@ static void parse_function(scc_ir2mcode_ctx_t *ctx, scc_ir_func_t *func) {
LOG_FATAL("<invalid block>\n");
return;
}
parse_bblock(ctx, bblock);
bblock_offsets[i] = scc_vec_size(ctx->mcode.mcode);
parse_bblock(ctx, bblock, &patches);
}
// 回填所有跳转偏移
u8 *buf = scc_vec_unsafe_get_data(ctx->mcode.mcode);
scc_vec_foreach(patches, idx) {
patch_t *p = &scc_vec_at(patches, idx);
usize target_id =
(usize)scc_hashtable_get(&ref2id, (void *)(usize)p->target_bb_ref);
usize target_off = bblock_offsets[target_id];
usize next_off = p->pos;
i32 rel = (i32)(target_off - next_off);
// 写入到指令的偏移字段(小端)
*(u32 *)(&buf[p->pos - 4]) = rel;
// buf[p->pos + 1] = (u8)(rel >> 0);
// buf[p->pos + 2] = (u8)(rel >> 8);
// buf[p->pos + 3] = (u8)(rel >> 16);
// buf[p->pos + 4] = (u8)(rel >> 24);
}
scc_free(bblock_offsets);
scc_vec_free(patches);
scc_hashtable_drop(&ref2id);
}
void scc_ir2amd64(scc_ir2mcode_ctx_t *ctx) {

View File

@@ -50,6 +50,20 @@ scc_hashtable_t *scc_reg_alloc_with_stack(scc_reg_alloc_t *ctx,
(void *)scc_vec_size(ctx->reg_loc_vec));
break;
}
case SCC_IR_NODE_CALL: {
scc_ir_type_t *ret_type =
scc_ir_ctx_get_type(ctx->ir_ctx, node->type);
if (ret_type && ret_type->tag != SCC_IR_TYPE_VOID) {
loc.kind = SCC_REG_KIND_STACK;
loc.idx = ctx->alloc_stack_size;
ctx->alloc_stack_size += 8;
scc_vec_push(ctx->reg_loc_vec, loc);
scc_hashtable_set(&ctx->node_ref2reg_loc,
(void *)(usize)node_ref,
(void *)scc_vec_size(ctx->reg_loc_vec));
}
break;
}
default:
break;
}

View File

@@ -6,7 +6,7 @@
#include <stdio.h>
void test_example(const char *input, cbool need_sema) {
void test_example(const char *input, cbool need_sema, const char *name) {
int res = 0;
scc_sstream_t mem_stream;
res = scc_sstream_init_by_buffer(&mem_stream, input, scc_strlen(input),
@@ -42,7 +42,10 @@ void test_example(const char *input, cbool need_sema) {
const sccf_t *sccf = sccf_builder_to_sccf(&mcode_ctx.builder);
scc_pe_builder_t pe_builder;
sccf2pe(&pe_builder, sccf);
scc_pe_dump_to_file(&pe_builder, __FILE__ "/../../test.exe");
char fname[1024] = {0};
scc_snprintf(fname, sizeof(fname), "%s%s%s", __FILE__, "/../../", name);
scc_pe_dump_to_file(&pe_builder, fname);
}
int main() {
@@ -54,6 +57,31 @@ int main() {
" a = a - b + 1;\n"
" return a;\n"
"}\n",
true);
true, "02_decl_expr.exe");
test_example("int main(void) {\n"
" int a;\n"
" a = 1;\n"
" if (a) {\n"
" a = 1;\n"
" } else {\n"
" a = 2;\n"
" }\n"
" return a;\n"
"}\n",
true, "04_if.exe");
test_example("int main() {\n"
" int i = 0;\n"
" while (i < 10) i = i + 1;\n"
" return i;\n"
"}\n",
true, "07_while.exe");
test_example("int add(int a, int b) {\n"
" return a + b;\n"
"}\n"
"\n"
"int main(void) {\n"
" return add(1, 2);\n"
"}\n",
true, "10_call.exe");
return 0;
}

View File

@@ -1,6 +1,6 @@
#include <stdio.h>
#include "scc_stdio.h"
int main(void) {
printf("hello world");
puts("hello world");
return 0;
}
}

13
tests/simple/expect.toml Normal file
View File

@@ -0,0 +1,13 @@
[return_val_cases]
"./01_return.c" = 65536
"./02_decl_expr.c" = 1
"./03_decl_init.c" = 11
"./04_if.c" = 1
"./05_else.c" = 2
"./06_fcall.c" = 3
"./07_while.c" = 10
"./08_do_while.c" = 128
"./09_for.c" = 10
"./10_main.c" = 3
"./11_recursive.c" = 120
[stdout_val_cases]

6
tests/simple/scc_stdio.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef __SCC_STDIO_H__
#define __SCC_STDIO_H__
extern int puts(const char *str);
#endif

View File

@@ -1,84 +1,89 @@
from pprint import PrettyPrinter
import subprocess
import os
from pathlib import Path
import tomllib
# 配置参数
TEST_DIR = Path(".")
CC_PATH = Path("../../src/smcc.exe")
VM_PATH = Path("../rv32-vm.exe")
WORKSPACE = Path(".") # 测试工作目录
WORKSPACE = Path(__file__).resolve().parent # 测试工作目录
TEST_DIR = Path(WORKSPACE)
CC_PATH = Path(WORKSPACE / "../../build/dev/scc")
# 测试用例映射表(示例)
TEST_CASE_MAP = {
"./01_return.c": 65536,
"./02_decl_expr.c": 1,
"./03_decl_init.c": 11,
"./04_if.c": 1,
"./05_else.c": 2,
"./06_fcall.c": 3,
"./07_while.c": 10,
"./08_do_while.c": 128,
"./09_for.c": 10,
"./10_main.c": 3,
"./11_recursive.c": 120,
}
def run_command(cmd, capture_stderr=True):
"""执行命令并捕获stderr"""
result = subprocess.run(
cmd,
cwd=WORKSPACE,
stderr=subprocess.PIPE if capture_stderr else None,
text=True,
timeout=1,
)
return result.stderr.strip() if capture_stderr else None
def run_command(cmd, capture_output=True):
"""执行命令并捕获 stdout"""
try:
result = subprocess.run(
cmd,
cwd=WORKSPACE,
stdout=subprocess.PIPE if capture_output else None,
stderr=subprocess.PIPE,
text=True,
timeout=5, # 增加超时时间以防虚拟机启动慢
)
# 返回 stdout 用于获取返回值,同时检查是否有运行时错误
return result.stdout.strip(), result.stderr.strip(), result.returncode
except subprocess.TimeoutExpired:
return None, "Timeout expired", -1
except Exception as e:
return None, str(e), -1
def run_test(test_file, expected):
print(f"\nTesting {test_file}...")
# 1. 编译生成flat.bin
compile_cmd = [str(CC_PATH), str(test_file)]
compile_err = run_command(compile_cmd)
# 1. 编译
compile_cmd = [str(CC_PATH), str(test_file), "-o", "test.exe"]
# 编译时关注 stderr 和返回码
_, compile_err, compile_ret = run_command(compile_cmd)
if not (WORKSPACE / "flat.bin").exists():
exe_path = WORKSPACE / "test.exe"
if not exe_path.exists() or compile_ret != 0:
print(f" Compilation failed: {compile_err}")
return False
# 2. 执行虚拟机
vm_cmd = [str(VM_PATH), "flat.bin"]
# 2. 执行虚拟机并获取输出
vm_cmd = [str(exe_path)]
actual_output, vm_err, vm_ret = run_command(vm_cmd)
# 3. 解析返回值(假设最后一行是返回值)
try:
vm_err = run_command(vm_cmd)
actual = int(vm_err.split()[-1])
except (ValueError, IndexError) as e:
print(f" Invalid VM output: {vm_err}")
return False
except subprocess.TimeoutExpired:
print(" Timeout expired")
# 如果存在 stderr 且返回码异常(例如负数表示信号终止),则视为运行时错误
if vm_err and vm_ret < 0:
print(f" Runtime error: {vm_err}")
return False
# 3. 获取返回值 (修改进程返回值而非 stdout)
actual = vm_ret
# 4. 验证结果
# 注意toml 中读取的 expected 可能是整数actual 也是整数,直接比较
if actual == expected:
print(f" PASSED {test_file}")
return True
else:
print(f" FAILED: Expected {expected}, got {actual}")
print(f" FAILED: Expected '{expected}', got '{actual}'")
return False
def main():
passed = 0
total = 0
config = {}
config_path = WORKSPACE / "expect.toml"
if not config_path.exists():
print(f"Config file not found: {config_path}")
return
for test_file, expected in TEST_CASE_MAP.items():
with open(config_path, "rb") as f:
config = tomllib.load(f)
PrettyPrinter().pprint(config)
for test_file, expected in config.get("return_val_cases", {}).items():
total += 1
if run_test(TEST_DIR / test_file, expected):
passed += 1
# 清理中间文件
if (WORKSPACE / "flat.bin").exists():
os.remove(WORKSPACE / "flat.bin")
exe_path = WORKSPACE / "test.exe"
if exe_path.exists():
os.remove(exe_path)
print(f"\nTest Summary: {passed}/{total} passed")