是的, 还有几个节点的指令生成没有测试呢, 包括流控制语句, IO. 不过呢, 流控制是相对比较容易优化的, 进行"生成指令再对比"的测试不太好维护, 而 IO 指令很简单: 所以要不这样, 把指令写出来, 然后开个虚拟机运行, 如果运行没问题就算测试通过了.
语义错误处理
一些常见的语义错误处理在之前的之前的内容中都以注释形式出现, 不过如果留心的话, 上次更新的代码这些错误处理都是完备的, 比如在 jerry-ui.h 和 jerry-ui.c 两个文件中给出了这些错误的 UI, 而 const.h 中更新了很多字符串字面常量宏作为错误信息. 当然你也可以随意修改, 随便它们怎么运作. 这里只是稍带提一下, 以免等会儿突然见到会显得很突兀.
/* 一般语义错误 */
void semanticErr(ErrMsg err, int line)
{
fprintf(stderr, "Error occurs at line %d:\n"
" %s\n",
line, err);
failed = 1;
}
/* 符号错误* /
void symError(ErrMsg err, struct VariableNode* var)
{
fprintf(stderr, "Error occurs at variable `%s', line %d:\n"
" %s\n",
var->ident, var->line, err);
failed = 1;
}
参数处理
参数处理目前采用比较笨的方法, 就是逐个参数进行比较. 比如, 现在允许参数有
/* 输出帮助信息 */
#define OPTION_HELP_INFO "--help"
/* 指定目标文件 */
#define OPTION_OUTPUT_SPECIFY "-o"
参数解析循环
static int showHelp(int argc, char* argv[]);
static int setOutputFileName(int argc, char* argv[]);
static int setFileToProcess(int argc, char* argv[]);
void handleParam(int argc, char* argv[], FILE** out)
{
int i = 1;
if (1 == argc) {
cmdParamError("Jerry: too few arguments.");
}
while (i < argc) {
if (0 == strcmp(OPTION_HELP_INFO, argv[i])) {
i += showHelp(argc - i - 1, argv + i + 1);
} else if (0 == strcmp(OPTION_OUTPUT_SPECIFY, argv[i])) {
i += setOutputFileName(argc - i - 1, argv + i + 1);
} else {
i += setFileToProcess(argc - i, argv + i);
}
++i;
}
*out = fopen(outFileName, "wb");
if (NULL == *out) {
cmdParamError("output file access error.");
}
}
每个分支匹配一个参数, 然后传入剩余的参数到具体的处理函数中去. 而每个处理函数的返回值则是它所消耗的参数个数, 以便于循环中调整参数指针.
指令写出
其它边边角角的东西不细说了, 现在切换到 jerry-compiler.c 文件中. main 函数中并没有写出的操作, 在调用了 handleParam 之后只是为语法分析进行铺垫工作和资源分配之类的. 嗯, 另有一个地方比 main 函数更适合完成这项工作, 那就是压在语法分析器栈栈底的无名英雄 FakeDefaultAnalyser 的 consumeNonTerminal 函数.
很显然, 当这个函数被调用时, 如果没有任何错误, 那么一定是其它所有分析器都完成任务正常退出了, 因此, 传递给它的语法节点就是表征整个源文件的语法树. 那么只需要将这个节点的指令序列输出就搞定了.
void fakeDefaultAnalyserConsumeNT(void* self, struct AbstractSyntaxNode* node)
{
if (NULL == node) {
printf("Nothing returned.\n");
} else {
node = (struct AbstractSyntaxNode*)newBasicBlockNode(node);
initStack(&loopStack);
initialSymTabManager();
struct List* insList = node->createInstruction(node);
finalizeSymTabManager();
loopStack.finalize(&loopStack);
if (isFailed()) {
while (0 != insList->count(insList)) {
revert(insList->popElementAt(insList, 0));
}
fprintf(stderr, "Error in source code.\n");
} else {
// 这里写出指令
}
}
现在要做的是, 首先给每条指令烙上偏移值, 以便于让跳转指令运作 --- 跳转指令都会变为一条整数参数指令.
InstructionCode code;
int segOffset = 0;
struct Iterator* iterator;
struct NoParamInstruction* endProg = (struct NoParamInstruction*)
allocate(sizeof(struct NoParamInstruction));
endProg->code = END_PROGRAM;
// 加入最后一条结束指令
insList->addTo(insList, endProg, insList->count(insList));
for_each (iterator, insList) {
code = ((struct AbstractInstruction*)
(iterator->current(iterator)))->code;
((struct AbstractInstruction*)
(iterator->current(iterator)))->segOffset = segOffset;
segOffset += sizeof(InstructionCode);
// 计入参数偏移量
if (isJump(code) || isIntParamIns(code)) {
segOffset += INT_SIZE;
} else if (isRealParamIns(code)) {
segOffset += REAL_SIZE;
}
}
然后写出总的指令大小, 或者可以称之为山寨版的代码段总长度
if (1 != fwrite(&segOffset, sizeof(segOffset), 1, treeout)) {
fprintf(stderr,
"An error occurs while writing instructions to file.\n");
exit(1);
}
最后一步, 将每个指令写出
for_each (iterator, insList) {
// 实现在下面
writeInstruction(iterator->current(iterator));
}
// 回收指令空间
while (0 != insList->count(insList)) {
revert(insList->popElementAt(insList, 0));
}
/* 逐条指令写出 */
void writeInstruction(void* instruction)
{
InstructionCode iCode = ((struct AbstractInstruction*)instruction)->code;
if (1 != fwrite(&iCode, sizeof(InstructionCode), 1, treeout)) {
fprintf(stderr, "Error occur when flush instructions to file.\n");
exit(1);
}
// 带参数指令写出参数
if (isIntParamIns(iCode)) {
if (1 != fwrite(&(((struct IntParamInstruction*)instruction)->param),
INT_SIZE, 1, treeout)) {
fprintf(stderr, "Error occur when flush instructions to file.\n");
exit(1);
}
} else if (isRealParamIns(iCode)) {
if (1 != fwrite(&(((struct RealParamInstruction*)instruction)->param),
REAL_SIZE, 1, treeout)) {
fprintf(stderr, "Error occur when flush instructions to file.\n");
exit(1);
}
} else if (isJump(iCode)) {
// 将指针参数转换为段间偏移量, 写出该偏移量整数
int offset = ((struct JumpInstruction*)instruction)
->targetIns->segOffset
- ((struct JumpInstruction*)instruction)->segOffset;
if (1 != fwrite(&offset, INT_SIZE, 1, treeout)) {
fprintf(stderr, "Error occur when flush instructions to file.\n");
exit(1);
}
}
}
请 update Jerry 以获取最新源代码.
分享到:
相关推荐
*部分主要讨论LuaAPI和虚拟机实现,包括二进制chunk格式、Lua虚拟机指令集、元编程、错误处理等。第二部分主要讨论Lua语法和编译器实现,包括词法分析、语法分析、代码优化、代码生成等。第三部分主要讨论Lua辅助API...
标题中的“D Parser 之前:写一个简单的虚拟机”指的是在构建D语言解析器之前,先设计和实现一个简单的虚拟机。虚拟机是许多编程语言解释执行的基础,它模拟了真实的计算机硬件,允许编译后的代码在不同平台上运行。...
了解和掌握Java虚拟机规范对于Java开发者来说是非常有必要的,这不仅可以帮助开发者写出更加规范的代码,还能让他们更好地理解和优化Java程序的性能,以及使用JVM提供的高级特性。对于实现Java虚拟机的技术人员来说...
题目中的两个乘法算法示例,分别展示了如何在虚拟机指令集的限制下,通过循环和累加实现乘法运算。下面,我们将对这两个方法进行详细分析。 ##### 方法1详解: 1. **初始化**:设置寄存器C和D为0,准备用于累加...
器,它的目标平台是一款馆主编写的虚拟机;而目标代码也是经过 馆主加强的一套工业汇编指令。但这不能妨碍My-C成为一款非常好 玩的C语言编译器。 它实现了95%以上的C89语法,并添加改造了有趣的内嵌汇编语言;它...
QEMU支持动态翻译,通过将目标代码转换为宿主机上的机器指令来实现快速执行。此外,QEMU还可以配合KVM(Kernel-based Virtual Machine)模块,实现半虚拟化,以提高性能。 二、易语言控制QEMU虚拟机 易语言以其直观...
通过学习,开发者能够避免常见的性能瓶颈,编写出更高效的代码。 7. **JVM调优工具**:介绍了如JConsole、VisualVM、JProfiler等用于监控和诊断JVM状态的工具,以及如何使用这些工具进行问题定位和性能分析。 8. *...
Sun JAVA虚拟机,全称Java Virtual Machine,是Java平台的核心组成部分,它负责解析并执行Java程序的字节码,使得Java代码能够...对于Java程序员来说,深入理解和掌握JVM的工作原理,有助于写出更高效、更稳定的代码。
虚拟机是一种软件模拟的计算机,它可以运行在真实的硬件上,模拟出另一种计算机环境,使得特定的软件可以在不同的操作系统或硬件平台上运行。C语言实现的MIPS虚拟机,就是通过C语言代码来模拟MIPS处理器的指令集和...
这可以帮助开发者写出更高效的代码,并确保应用程序能够在各种环境下稳定运行。 #### Java虚拟机的基础知识 - **数据类型支持**:JVM支持多种基本数据类型,包括但不限于: - `byte`:占用1字节 - `short`:占用...
Java虚拟机(JVM)是Java编程语言的核心组成...深入理解JVM不仅有助于写出高效稳定的Java程序,还能帮助开发者解决复杂的运行时问题。通过持续学习和实践,开发者能够更好地驾驭Java虚拟机,提高软件系统的整体性能。
本脚本系统主要面向内存和... 编译效率非常高(编译出的指令集接近手写!), 同时提供虚拟机源码, 可以随时按照自己的需求改进虚拟机, 附送29页完整教程和一个现成的J2ME演示包. 看完后能使你的手机项目架构上多一种选择.
【标题】"使用Java写的GVmaker虚拟机(开源)"是一个关于使用Java编程语言开发的GVmaker虚拟机项目,该项目是开源的,意味着它的源代码可供公众查看、学习和改进。这个虚拟机可能是为了理解虚拟机的工作原理,或者作为...
高效实现:探索 Java 编译器,以及内嵌于 Java 虚拟机中的即时编译器,帮助你更好地理解 Java 语言特性,继而写出简洁高效的代码; 代码优化:介绍如何利用工具定位并解决代码中的问题,以及在已有工具不适用的情况...
在 `_检测按钮_被单击` 子程序中,如果 `检测虚拟机` 返回 `真`,程序会弹出一个对话框提示用户程序不能在虚拟机中运行;如果返回 `假`,则提示程序正在物理机上运行。 总的来说,这段代码提供了在易语言环境中检测...
这些知识能够帮助开发者写出更高效、更稳定的Java应用。 另外,JVM的规范和实现经历了多个版本的发展,例如JDK 8和JDK 11中有重大更新。每个版本都带来了新的特性,比如JDK 8中的Lambda表达式、新的日期时间API,...
可以看到 fc4_iso 的目录,并且可以用 cp 等指令实现 Windows 到 Linux,Linux 到 Windows 的读写操作了。 VMWare Tool 的安装和共享文件设置可以增强虚拟机的性能和功能,方便虚拟机和主机之间的文件共享。