apollo-11的AGC

Twitter上Lin Clark骄傲的宣布自己老妈Magret Hamilton 50年前写的Apollo 11登月代码被人推上了GitHub库。

的确是一件让人骄傲的事情。我挖了下,Hamilton当时写的的显示接口部分。详细见: http://www.ibiblio.org/apollo/
但同时,她是AGC的Programming Leader。

也很感叹网上一堆人才们开发了AGC(Apollo Guidance Computer)的模拟器,https://github.com/virtualagc/virtualagc。
毕竟是人类第一次登月。第一次开发core rope这种原始的存储设备,第一次提出软件工程的概念,第一次利用我们现在看来原始的代码
帮助人类送到从未触及的月球。放在现在,我们很多技术人员还没有当年这拨程序员的水平。即使,我们有更先进的设备和技术。
Apollo-11是一个组织,一个政府,以及人类共同的集体合作结果。

以此纪念AGC提出的软件工程概念,在还没有C语言的年代:https://github.com/chrislgarry/Apollo-11

使用指针7年的C程序员问了一个问题

今天小Y发来消息,说自己被一个代码弄的焦头烂额。大概是这个样子


unsigned char phSessionHandle[20] = {0};
SDF_GenerateRandom(phSessionHandle,uiLength,ucRandom);//1
SDF_GenerateRandom(*(void**)phSessionHandle,uiLength,ucRandom);//2
SDF_GenerateRandom(*(void**)&phSessionHandle,uiLength,ucRandom);//3

函数SDF_GenerateRandom的原型是:
unsigned int SDF_GenerateRandom(void* hSessionHandle, unsigned int uiLength, unsigned char ucRandom)

就是这么简单的一段代码,小Y说:“1,3方式的调用我得到了期望的结果。但是2方式错了”。别忘了,小Y可是一个有7年C语言编程经验的程序员。
这种传指针的把戏怎么可能难住TA。我们来看看。[read]
1符合大家的使用习惯,一个数组unsigned char [20]类型传入到函数,自动退化为一个unsigned char*指针类型,最终变成void*类型。不会出现什么问题。

2是一个 unsigned char[20] 类型被强转为 void** 类型。最终我们用*解引用,作者目的是想让它强制为一个 void* 类型,这样就和函数原型声明一致了(难道是因为静态检查让作者做出的改动?奇妙的是,调用2并没有返回期望的结果。

3是一个 unsigned char[20] 类型被取地址后变成一个 unsigned char* [20] 类型。很明显,作者在发现2调用出现问题后,觉得可能是因为自己没有取地址的原因,特意加上&,让(&phSessionHandle)看起来是一个二级指针。这样名正言顺的变成 void **,最后解引用为void*。小Y顺利运行成功,感觉“对”了。

有过这次经验,小Y每次碰到数组就这么用。在一次使用memcpy的代码中,TA这样写。


memcpy(&phSessionHandle,&source,sizeof(source))

小Y的领导发火了,你干啥在数组前加一个取地址符号&啊。他可是一个“二级”指针啊。你应该这样用。
memcpy(phSessionHandle,&source,sizeof(source))

对于一个专注业务逻辑的程序员,小Y即使当了这么多年的码农,仍然很讨厌指针。
第一:同样是在数组上使用&,TA第一次用“对”了,第二次却不被领导认可。
第二:到底数组和指针在编译器内部如何被识别,又发生了哪些类型转换。

为了解答小Y的疑问。我们先来看看C11标准里怎么解释&运算符号。

查阅C11标准文档:

6.5.3 The unary & operator yields the address of its operand. If the operand has type ‘‘type’’,
the result has type ‘‘pointer to type’’. If the operand is the result of a unary * operator,
neither that operator nor the & operator is evaluated and the result is as if both were
omitted, except that the constraints on the operators still apply and the result is not an
lvalue. Similarly, if the operand is the result of a [] operator, neither the & operator nor
the unary * that is implied by the [] is evaluated and the result is as if the & operator
were removed and the [] operator were changed to a + operator. Otherwise, the result is
a pointer to the object or function designated by its operand.

翻译如下:

&单目操作符得到它操作数的一个地址。
如果操作数的类型是‘type’,那么结果是一个指向type的指针。
如果操作数是单目运算符*的结果。那么*和&都会被编译器忽略。除非操作符上叠加其它操作而且结果不是一个左值。
同样,如果操作数是[]运算符的结果。那么&和[]所隐含的*操作都不会被执行。结果和去掉&运算并且把[]变成+操作一样。(意思是可能存在[]偏移的情况)
其它情况,&操作符返回的结果是指向对象的指针或者操作数所指定的函数。

再来看C99的阐述,
rationale for the C99 standard C99标准补充提到取地址符号时说明

6.5.3.2 Address and indirection operators
Some implementations have not allowed the & operator to be applied to an array or a function.
(The construct was permitted in early versions of C, then later made optional.) The C89
Language Committee endorsed the construct since it is unambiguous, and since data abstraction is
enhanced by allowing the important & operator to apply uniformly to any addressable entity.

翻译如下:

一些(编译器)实现不允许&操作符用在数组和函数上。(这种搭配结构在早期的C版本中是允许的,后来变成一个可选项)。C89语言委员会认为这种搭配含义并不模糊,表示支持。因此,基于数据抽象概念被加强的原因,委员会允许&这个重要操作符用在任何可以寻址的实体对象上。

再回到我们的代码

SDF_GenerateRandom(*(void**)phSessionHandle,uiLength,ucRandom);//2
SDF_GenerateRandom(*(void**)&phSessionHandle,uiLength,ucRandom);//3

2的类型转换过程是:(2a) unsigned char [20] (编译器维护的一个类型)-> (2b) void ** -> (2c) void*
3的类型转换过程是:(3a) unsigned char [20] (编译器维护的一个类型)-> (3b) unsigned char* [20] (编译器维护的一个类型) -> (3c) void** -> (3d) void *

通过上面标准的解释我们最多也就知道在对3a->3b的过程中并没有发生实质性的类型变化。只是编译器维护的类型发生了变化,本质上[]隐含了一个一级指针在编译器看来做了点改动(为什么编译器要这样做?我们后面看)。

而实际上2得到了错误的结果,因为2a到2b这一个过程,将一个*强制变成了**的二级指针。这个二级指针是非法的。再去解引用(dereference *)是一个未定义的行为。对此标准也有这样的解释。

The unary * operator denotes indirection. If the operand points to a function, the result is
a function designator; if it points to an object, the result is an lvalue designating the
object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an
invalid value has been assigned to the pointer, the behavior of the unary * operator is
undefined.102)

而为什么3的运行结果是对的。我能想到的是小Y的编译环境正好对这种未定义行为解释到一个合法的地址返回了合法值,并没有发生段错误罢了。3a到3b的过程中并未发生实质的类型变化。程序不可能记住了这个非法的二级指针,在解引用后指回。

于是我在Mac上写了一段类似的程序。dereference以后指针并没按照期望变回原来的地址,每次运行都是一个变化的地址,并导致了段错误。说明这种代码是完全不可移植。我们在平时要完全禁止。

值得一说的是,编译器在报错是经常会带出 type*[] 和 type* 不匹配的警告。
所以我们可以再上升一下:看看Edwin Brady 在《Type-driven Development with Idris》怎么描述不同“物体”怎么观察到程序的类型的

Types serve several important roles:
For a machine, types describe how bit patterns in memory are to be interpreted.
For a compiler or interpreter, types help ensure that bit patterns are interpreted consistently when a program runs.
For a programmer, types help name and organize concepts, aiding documentation and supporting interactive editing environments.

翻译:

类型扮演几个不同的重要角色:
从一台机器看来(0和1),类型用来描述内存中0,1排列组合而成比特流怎么被解释。(我给翻译多加了点料)
对编译器和解释器而言(它们看到int [],char[]),类型用来保证0,1排列组合而成的比特流在程序运行期间有一致的解释结果。(比如 char a + int b 到底得到一个char还是int,是否允许这种操作。说大白话就是让解释器可以做很多中间转换,编译检查等)
对程序员而言(我们看到int,char),类型帮助我们命名和组织概念,书写文档说明支持交互式编辑环境(有具体类型来承载变量,向代码阅读者解释说明,支持IDE等给出提示检查)

[/read]

2018转眼到八月

看下前面的博文订了不少计划,结果都没有好好去执行。

昨天看了电影《巨齿鲨》,算算很久没看电影了。

今天在家看笑来的《把时间当朋友》

老妈早回家了,老爸住院,老姐赶回家照顾。真心觉得有姐姐还是挺好的。

2018年转眼8月份,剩下4个月我能做些什么让我生活变的更好。

除了sketch plan,还有hi-benchmarks.至少今年还要搞几本书和写一个自己的软件项目吧。

书可能涉及:性能之巅,AI硬件架构,GEB lefted over.

总之,就四个月了。丢。。。

读 程序员的自我修养

叫这个名字的书有两本。一本偏个人指导,一本是偏实践 [read]

“修养” 其实很宽泛的用词,谈到修养。我更愿意用“素质”这个词来替代。“素质”听起来更加具体可操作。

应用比较阅读的指导原则我们来了解这两本书。


作者:陈逸鹤
书名:程序员的自我修养
chenyihe paperwork

作者是一个全栈开发者,初识这本书最打动我的一句话:“成为管理者并不是让你去支配他人,或让其他人替你做事。管理者需要理解你的团队成员的长处和不足,并知道如何以服务的态度使团队获得最大化产出”


作者:俞甲子
书名:程序员的自我修养

programmer paperwork


最后推荐一本

作者:Andy Hunt
书名:程序员思维修炼

andy
[/read]

LuaJIT的LightUserdata使用问题

LuaJIT提供诱人的虚拟机代码加速,诸多开源软件使用它作为自己的解释器。[read more=”点击阅读更多” less=”收起阅读”]但由于LuaJIT在开发初期并没有一个尝试兼容所有CPU架构的设计框架,导致现在无法使用LightUserData这种简单的数据结构。LuaJIT自身在不断演进的同时,每个依赖它的开源社区都给出自己的规避方案。

那么,什么是LightUserData?

它是Lua中一个用来存储指针的轻量级数据类型。该数据类型在LUAJIT中不支持垃圾回收,它可以用来指向任一userdata数据类型。

lightuserdata通常用来指向lightdata所在地址。参看:https://www.lua.org/pil/28.5.html

API接口:
void lua_pushlightuserdata (lua_State *L, void *p)

文档说明如下:
Pushes a light userdata onto the stack.
Userdata represent C values in Lua. A light userdata represents a pointer. It is a value (like a number): you do not create it, it has no individual metatable, and it is not collected (as it was never created). A light userdata is equal to “any” light userdata with the same C address.

FireFox社区:
解决sparcx64 aarm64上跑firefox
https://bugzilla.mozilla.org/show_bug.cgi?id=1143022
https://bugzilla.mozilla.org/show_bug.cgi?id=1275204

Debian社区:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=818616

openResty社区:
https://github.com/openresty/lua-nginx-module/issues/757
https://github.com/openresty/lua-nginx-module/issues/1152

LuaJIT社区中Issue跟踪:
https://github.com/LuaJIT/LuaJIT/issues/156

luaJIT官网版本发布列表:
http://www.luatex.org/svn/tags/0.98.1/source/libs/luajit/LuaJIT-src/doc/changes.html

官网对当前状态中GC64模式不支持>47bit的VA:
luajit-2.0/doc/status.html

做出的说明如下:
• LuaJIT on 64 bit systems provides a limited range of 47 bits for the legacy lightuserdata data type. This is only relevant on x64 systems which use the negative part of the virtual address space in user mode, e.g. Solaris/x64, and on ARM64 systems configured with a 48 bit or 52 bit VA. Avoid using lightuserdata to hold pointers that may point outside of that range, e.g. variables on the stack. In general, avoid this data type for new code and replace it with (much more performant) FFI bindings. FFI cdata pointers can address the full 64 bit range.

luaJIT beta3更新日志:
http://repo.or.cz/luajit-2.0.git/shortlog/refs/tags/v2.1.0-beta3

linaro某位同学的私人proposal:
https://github.com/zhongweiy/LuaJIT/commit/3fa648b6d8b7ecd2d06ce8397089f244564265bb
https://github.com/zhongweiy/LuaJIT/commit/dc3bd1626b4c28da7aa58da30bbd174e73badb65
https://www.freelists.org/post/luajit/fix-lightud-type-for-48bit-virtual-address
https://www.freelists.org/post/luajit/Proposals-for-fixing-light-userdata-issue-on-virtual-address-47-bits-platform
很遗憾的是这位同学临时的patch不能合入,Mike Pall 本人建议涉及到在ARM64上使用lightuserdata的代码全部使用FFI代替。这意味着已有的代码,如果想在ARM64上使用LUAJIT来加速,你不得不写C代码。对于一些大型项目,这将是个灾难。 :)

资料:
Linaro’s  effort to port LuaJIt to AARM64:  Sep 29, 2016

Server Ecosystem: Xen on ARM, from Big Iron to IoT & LuaJIT status on Aarch64 Speakers: Ryan Arnold, Steve Capper, Julien Grall, Zheng Xu
https://www.youtube.com/watch?v=ZTtCHF4FoqM&feature=youtu.be

Linaro&csico 着手解决ARM64生态问题,开始支持LuaJIT
https://collaborate.linaro.org/display/TCWGPUB/LuaJIT+for+ARM64
https://github.com/sindrom91/LuaJIT/commit/d63e0af3978f24e230cccc017ca98ed1653497de

Mike Pall开发arm64版本:
https://github.com/cbaylis/luajit-aarch64
[/read]

读书的心情

这是 2015年09月17日读完 《how to read a book》后的笔记,汇总到此。[read more=”点击阅读更多” less=”收起阅读”]

 how to read a book--cover
我怀着敬畏的心来阅读一本书,就像和作者在对话。

 

我紧张害怕,生怕不能明白作者的意图。
我毫无自信,像是无知的娃娃。
记不住那些,错过那些能让我顿悟的话语。
读书相当于是老师,不同的是疑问需要自己回答。

 

总结阅读层次:
第一层次:基础阅读,字面上的理解。
第二层次:检视阅读,速度加快, skimming systematically。原来只需要粗略翻阅一本书的时候,却拿出了仔细阅读,完全理解一本书的时间。这加重了阅读的困难。
第三层次:分析阅读,全盘的阅读,完整的阅读,优质的阅读,直到这本书成为他自己。分析阅读就是要咀嚼和消化的一本书,追求理解。
最高层次:主题阅读,syntopical reading,又叫comparative reading。分类比较,优劣比较,观点汇总,筛选出适用于自己的精华。
 
 
最后觉得作者还欠读者一个

终极层次

:融入生活,感受真假。读书只是辅助式学习,很大程度上是最低廉同时也是最低效的学习。所以,并不是最有效的学习方式。

#but#。在没有最有效的学习方式之前,书本是最有效,成本最低,性价比最高的得到新知识的方法。

[/read]