亚当2014年的演讲

鼓励人们,去尝试一下.干就完了.
能用D写一个内核吗?能.
接口中,不能有虚函数,因为你不知道有多少.
一旦重载新(模板函数)运算符,如何放进类中?答案很简单:只需转发它给虚函数,你就有了这些命名函数.理念:实用为主.
在D中搞内核,处理器中跑的每行代码都是你的,这很酷.
我们需要先编译个你好!.从空object.d开始,链接器中去掉运行时,看发生什么.大胆干,未定义引用?没什么大不了的.
1,缺少TypeInfo_Struct,好,加1个.2,大小不匹配,搜索该错误消息,发现在typinf.c中说,需要52字节.我用void*静态数组,而别人用ubyte数组.不断搜索,然后把要用的抓回来.如_d_arrayliteralSOMETHING.然后搜索d运行时,发现在lifetime.d中.然后复制进你的object.d中.你只需要大小名字匹配,类型不用匹配.
然后,你得到一个最小对象类:Object,Throwable类,Exception,ModuleInfo构,及一些让编译器闭嘴的TypeInfos.
简单用-nostdlib选项,也不要C库.最终得到非常小(3kb)的可执行文件.它能编译并运行.
现在需要入口点,这里有内联汇编.也是我用dmc原因,内联汇编很好用.与独立汇编器编写内容相同.它可能无法破坏寄存器,或自动推导寄存器上的C变量类型,但他管用,且很易理解.
然后是,定义入口点,编译器期望_d_run_main函数.然后是在林操上跑,定义了它后,调用exit(2)系统函数.运行了,没有段错误!
同样,如果写编译期函数,如果不知道结果,你先按运行时来写并调试,然后用mixin(插件),并用pragma(msg)类似调试.编译时运行很大的语言子集.所以运行时管用的,基本只要稍微修改,编译时也管用.
裸机并不特殊,它只是运行代码而已.你可用GRUB引导器,甚至不必改文件格式,只需要说,嘿GRUB,加载这个ELF文件,变得非常容易.我用dmd开发.
运行起来后,可加越来越多函数.要区分object.d编译器的功能.
首先是,alias string = immutable(char)[]别名.d语言中没有,但有字符数组.只是object.d中的别名.
然后是typeinfo,你得到要匹配大小的类型信息,编译器输出它.对typeinfo_class,你得到编译器输出的初化数组,及按静态数据写的虚表指针.
你必须要匹配它.我们是想让__traits可用,
如,你想有classInstanceSize特征,但你不能初化,如果你想创建类:1,分配内存.2,复制初始数据互它.3,调用构造函数.这是new三步曲.
如果不用typeid().init,用trait呢?如果typeinfo选入的呢.你只要有你需要的初化器.然后随意布局,这样空的object.d,就够了.
并非所有TypeInfo都这样,用TypeInfo_Struct时,然后加个整数组,其有个叫nextTypeInfo成员.用typeid(数组),得到TypeInfo_Array.而.nextint[TypeInfo_i].也可用is()来取.但我让编译器来搞.
typeinfo有趣的是,在rt/typeinfo里面有大堆手写文件.他说int4大小覆盖.编译器现在都知道,有插件/循环,现在可自动化.
现在可以,foreach( TypeTuple!(所有内置类型)),生成__traits中拉出来并插件起来造你的TypeInfos.
仔细看看TypeInfo_i名字等,这是混淆名.i混淆.Aya不变符数组混淆名.编译器知道它,当你插件type.mangleof时,会形成它.相当于说编译时构造类型,你不必手动运行时类型.
扩展它很有意思,如果以typeinfo关联数组,你不必修改运行时,就可扩展.如,你想按打印任意类型数据.用模板,从std.conv调用to!string即可.
但,就像d运行时,你只一块数据,多数函数为两个参数:空*数据+类型的TypeInfo(类型信息),用类型信息来取数据大小如何操作.
不用模板,造你自己的toString函数.提供空*为参.你只有普通接口及可任意扩展.即如何在编译时特征和运行时反射间架桥.
类似的是可为每个单独类型创建静态构造器.在object.d中,为RTInfo.只是现在enum RTInfo = null.每个用户定义类型都实例化它.在此,你可静态检查类型,可运行时反射,而不必用lint工具.
如在object.d中挂钩该rtinfo,则直接应用于整个项目,而不是单个模块.库可定义自己的检查插件,单独放进去,虽然麻烦,但管用.
只要安装了反射,就可运行时一直用(查询).可对模块定义静态构造列表,编译器自动为你合并,可建立全局数据,供以后使用.
静态构造函数实际上是D运行时功能,编译器输出模块列表,有个_Dmodule_ref符号,链接器把它们放一堆,循环遍历时,编译器提供单元测试/静态构造/静态析构指针,如果去掉运行时,得你自己干这些.
回到程序入口点,你要先找到数据,循环并找到并跑它,然后来到实际函数,只需要20KB,不大.
需要大量运行时.类似的,如_d_dynamic_cast调用动态转换.
其,查找typeinfo[虚表0项],问是否是其他类的基,是,则继续并返回偏移对象.用_d_newclass分配新类.
D运行时,管理内存.垃集为不懂析构器的人准备的,在C++论坛,我会说你只要定义个带有析构函数的结构,垃集也是如此,你不再关心所有权.
有了垃集,只需要完成它,下一件事.相反.我写了个压指针内存分配器.分配好写,释放难.一旦写好,你可在裸机上用了.很多东西都有效.
虚函数/类成员都行.线程本地存储就不行了.操作系统不能为你定义线程缓冲区.
幸运,D还支持shared__gshared,一旦你这样做,现在可访问你放静态数据/全局变量下面堆栈段,并完成它.即使设法实现其余运行时功能,他们也会假设你有TLS.
d运行时立即分配线程本地数据,然后垃集为你分配线程上下文,正常时没问题,但你没有线程/垃集时,很烦人.
还要了解从哪释放内存.可用scope(exit/failure/success),在使用时就保证分配.
要准备好按C方式写.Phobos用一个函数,引入大量模块.
simpledisplay.d时,避免了标准库.6000行都很快.而标准库太慢.D1还没意识到,就编译完了.
运行时,禁用不变量和检查边界,_d_boundscheck_d_assertm,然后30kbModuleInfo.裸机时用-release和-noboundscheck编译.否则要定义invariant.d等等函数,很麻烦.
裸机最有趣是中断处理,循环(失败->重启).因而用内联汇编,无栈/无返回,所有都得自己写.
即使写微优化代码,安装栈帧,多次调用仍成本高,你要内联它.
进入中断器后,你要有函数指针+中断程序.你要保留使用的寄存器,处理中断,用(简单调用out的)中断控制器确认它,然后用iret返回,跑程序.
三个错误,你要反汇编,iretd.有时dmd32位上输出16位中断码.对双精,你要显式写iretd.
你忘记确认中断,又要重来.
下一步是告诉处理器中断在哪里.x86架构内存布局很怪,与8080兼容,然后不断加新东西.所以不是简单函数指针,分为三部分低16位,标志,然后有32位,低8位/高8位等.
三个错误,是结构对齐方式问题,Dalign/align(1).现在align(1) struct InterruptLocation对齐.一个是字段对齐,一个是结构对齐.
加载了中断,就该使用硬件了.它是映射内存的,只是个神奇的数组.
如在0xb8000指定位置写,就看见字母了.重载运算符不需要运行时.编译器只是重写为常规函数.
你可用内联汇编,你可写小插件函数来生成汇编,然后在高级层次上他们.你仍然是在写汇编.用D元编程来写汇编.
内联汇编只是让你无限灵活之一,irc上有人说想要标签地址作为变量.但这很丑,你可在运行时$取它.你可这样取裸函数地址.
传递闭包给分配内存函数时,没有垃集,如何取地址.你不要管,没有_d_allocmemory时,让它成为链接器错误.
闭包最佳方法是写自己的构或类,复制你特别需要数据,然后发送那个小对象.这样,很清楚发生什么,想要什么,需要什么及能够控制所有权,只是没内置函数好看.
继续写_d_allocmemory函数并返回块.如何释放?则分闭包为两个部分函数+数据指针.函数指针只是pod,数据指针可能为类/构/(指向复制到堆上的)随机栈帧指针,从数据中检查_d_allocmemory(a).我知道a来自的堆.如果在内存节内,则释放,否则,交给别人处理.
_d_string_switch函数,串上切换,它给你串针编译时选项,编译器排序了数组,你可二分搜索它.
自定义运行时,可了解系统是如何工作的.看到错误时,就知道去哪里找,如AA,完全是可怕的.一半像普通数组有typeinfo+指针,另一半在object.d中写啥也没干的关联数组(构).但如果没有后者,又链接不过,就会变成未定义Aya等等标识符.
我的cgi.d,最下面有void workAroundLinkerErrors,然后变成writeln(typeid()),来隐藏AA复杂性.太难了,太丑了.
知道函数名,可迅速定位,如gc_malloc.置断点,运行程序一气呵成.然后看见堆栈上有_d_arrayliteralX函数,数组字面中静态数据导致动态分配?是的.所以,就要小心,要在定义数组时字面静态调用它,来摆脱分配.
对构的填充字节,你可用.offsetof和.sizeof.你放他们进中然后用pragma(msg),编译器输出小图.你可用pragma(msg)输出图表,可用CTFE串读回它们.
我告诉D作者,串插件就像汇编,很易学习.看见mov EAX, 0,就是移动至寄存器.但如何构建程序(栈帧/算法)就难了.
为利用串插件,我喜欢编写个普通旧解析器,去掉,构建抽象语法树,并在其上操作,然后转换回并打印.分解为令牌,解析,操作,转串更容易,而不是直接操作一大段代码串.虽然代码更多,但维护性更好.而不是到处都是连接(~)运算符.~是邪恶的.生成大量临时对象.不要用它,而是重载~=.

posted @   zjh6  阅读(13)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示