diff --git a/SUMMARY.md b/SUMMARY.md index 5f99734..d69225e 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -16,11 +16,18 @@ * [2.4. CGO内存模型](ch2-cgo/ch2-04-memory.md) * [2.5. C++类包装](ch2-cgo/ch2-05-class.md) * [2.6. 静态库和动态库](ch2-cgo/ch2-06-static-shared-lib.md) - * [2.7. Go实现Python模块(TODO)](ch2-cgo/ch2-07-py-module.md) - * [2.8. SWIG(TODO)](ch2-cgo/ch2-08-swig.md) - * [2.9. 补充说明(TODO)](ch2-cgo/ch2-09-faq.md) -* [第三章 汇编语言](ch3-asm/readme.md) -* [第四章 移动平台](ch4-mobile/readme.md) + * [2.7. Go实现Python模块](ch2-cgo/ch2-07-py-module.md) + * [2.8. 补充说明](ch2-cgo/ch2-08-faq.md) +* [第三章 汇编语言(TODO)](ch3-asm/readme.md) + * [3.1. 汇编基础(TODO)](ch3-asm/ch3-01-basic.md) + * [3.2. 控制流(TODO)](ch3-asm/ch3-02-control-flow.md) + * [3.3. 包变量(TODO)](ch3-asm/ch3-03-var.md) + * [3.4. 叶子函数(TODO)](ch3-asm/ch3-04-leaf-func.md) + * [3.5. 动态栈(TODO)](ch3-asm/ch3-05-more-stack.md) + * [3.6. 垃圾回收(TODO)](ch3-asm/ch3-06-gc.md) + * [3.7. 例子: 图像降采样(TODO)](ch3-asm/ch3-07-pyrdown.md) + * [3.8. 补充说明(TODO)](ch3-asm/ch3-08-faq.md) +* [第四章 移动平台(TODO)](ch4-mobile/readme.md) * [第六章 Go和Web](ch6-web/readme.md) * [6.1. Web开发简介](ch6-web/ch6-01-introduction.md) * [6.2. Router请求路由](ch6-web/ch6-02-router.md) diff --git a/ch1-basic/ch1-01-genesis.md b/ch1-basic/ch1-01-genesis.md index 7a8afc0..d8a7685 100644 --- a/ch1-basic/ch1-01-genesis.md +++ b/ch1-basic/ch1-01-genesis.md @@ -50,7 +50,7 @@ func main() { 将以上代码保存到`hello.go`文件中。因为代码中有非ASCII的中文字符,我们需要将文件的编码显式指定为无BOM的UTF8编码格式(源文件采用UTF8编码是Go语言规范所要求的)。然后进入命令行并切换到`hello.go`文件所在的目录。目前我们可以将Go语言当作脚本语言,在命令行中直接输入`go run hello.go`来运行程序。如果一切正常的话。应该可以在命令行看到输出"你好, 世界!"的结果。 -现在,让我们简单介绍一下程序。所有的Go程序,都是由最基本的函数和变量构成,函数和变量被组织到一个个单独的Go源文件中,这些源文件再按照作者的意图组织成合适的package,最终这些package再有机地组成一个完成的Go语言程序。其中,函数用于包含一系列的语句(指明要执行的操作序列),以及执行操作时存放数据的变量。我们这个程序中函数的名字是main。虽然Go语言中,函数的名字没有太多的限制,但是main包中的main函数默认是每一个可执行程序的入口。而package则用于包装和组织相关的函数、变量和常量。在使用一个package之前,我们需要使用import语句导入包。例如,我们这个程序中导入了fmt包(fmt是format单词的缩写,表示格式化相关的包),然后我们才可以使用fmt包中的Println函数。 +现在,让我们简单介绍一下程序。所有的Go程序,都是由最基本的函数和变量构成,函数和变量被组织到一个个单独的Go源文件中,这些源文件再按照作者的意图组织成合适的package,最终这些package再有机地组成一个完整的Go语言程序。其中,函数用于包含一系列的语句(指明要执行的操作序列),以及执行操作时存放数据的变量。我们这个程序中函数的名字是main。虽然Go语言中,函数的名字没有太多的限制,但是main包中的main函数默认是每一个可执行程序的入口。而package则用于包装和组织相关的函数、变量和常量。在使用一个package之前,我们需要使用import语句导入包。例如,我们这个程序中导入了fmt包(fmt是format单词的缩写,表示格式化相关的包),然后我们才可以使用fmt包中的Println函数。 而双引号包含的“你好, 世界!”则是Go语言的字符串面值常量。和C语言中的字符串不同,Go语言中的字符串内容是不可变更的。在以字符串作为参数传递给fmt.Println函数时,字符串的内容并没有被复制——传递的仅仅是字符串的地址和长度(字符串的结构在`reflect.StringHeader`中定义)。在Go语言中,函数参数都是以复制的方式(不支持以引用的方式)传递(比较特殊的是,Go语言闭包函数对外部变量是以引用的方式使用)。 diff --git a/ch1-basic/ch1-06-goroutine.md b/ch1-basic/ch1-06-goroutine.md index 1149297..c11ed59 100644 --- a/ch1-basic/ch1-06-goroutine.md +++ b/ch1-basic/ch1-06-goroutine.md @@ -30,7 +30,7 @@ func main() { } ``` -因为`mu.Lock()`和`mu.Unock()`并不在同一个Goroutine中,所以也就不满足顺序一致性内存模型。同时它们也没有其它的同步事件可以参考,这两个事件不可排序也就是可以并发的。因为可能是并发的事件,所以`main`函数中的`mu.Unock()`很有可能先发生,而这个时刻`mu`互斥对象还处于未加锁的状态,从而会导致运行时异常。 +因为`mu.Lock()`和`mu.Unlock()`并不在同一个Goroutine中,所以也就不满足顺序一致性内存模型。同时它们也没有其它的同步事件可以参考,这两个事件不可排序也就是可以并发的。因为可能是并发的事件,所以`main`函数中的`mu.Unlock()`很有可能先发生,而这个时刻`mu`互斥对象还处于未加锁的状态,从而会导致运行时异常。 下面是修复后的代码: @@ -41,14 +41,14 @@ func main() { mu.Lock() go func(){ fmt.Println("你好, 世界") - mu.Unock() + mu.Unlock() }() mu.Lock() } ``` -修复的方式是在`main`函数所在线程中执行两次`mu.Lock()`,当第二次加锁时会因为锁已经被占用(不是递归锁)而阻塞,`main`函数的阻塞状态驱动后台线程继续向前执行。当后台线程执行到`mu.Unock()`时解锁,此时打印工作已经完成了,解锁会导致`main`函数中的第二个`mu.Lock()`阻塞状态取消,此时后台线程和主线程再没有其它的同步事件参考,它们退出的事件将是并发的:在`main`函数退出导致程序退出时,后台线程可能已经退出了,也可能没有退出。虽然无法确定两个线程退出的时间,但是打印工作是可以正确完成的。 +修复的方式是在`main`函数所在线程中执行两次`mu.Lock()`,当第二次加锁时会因为锁已经被占用(不是递归锁)而阻塞,`main`函数的阻塞状态驱动后台线程继续向前执行。当后台线程执行到`mu.Unlock()`时解锁,此时打印工作已经完成了,解锁会导致`main`函数中的第二个`mu.Lock()`阻塞状态取消,此时后台线程和主线程再没有其它的同步事件参考,它们退出的事件将是并发的:在`main`函数退出导致程序退出时,后台线程可能已经退出了,也可能没有退出。虽然无法确定两个线程退出的时间,但是打印工作是可以正确完成的。 使用`sync.Mutex`互斥锁同步是比较低级的做法。我们现在改用无缓存的管道来实现同步: diff --git a/ch2-cgo/ch2-01-hello-cgo.md b/ch2-cgo/ch2-01-hello-cgo.md index 84b9515..3ec308f 100644 --- a/ch2-cgo/ch2-01-hello-cgo.md +++ b/ch2-cgo/ch2-01-hello-cgo.md @@ -127,6 +127,32 @@ func SayHello(s *C.char) { } ``` + +现在中国版本的CGO代码中C语言代码的比例已经很少了,但是我们依然可以进一步以Go语言的思维来提炼我们的CGO代码。通过分析可以发现`SayHello`函数的参数如果可以直接使用Go字符串是最直接的。在Go1.10中CGO新增加了一个`_GoString_`预定义的C语言类型,用来表示Go语言字符串。下面是改进后的代码: + +```go +// +build go1.10 + +package main + +//void SayHello(_GoString_ s); +import "C" + +import ( + "fmt" +) + +func main() { + C.SayHello("Hello, World\n") +} + +//export SayHello +func SayHello(s string) { + fmt.Print(s) +} +``` + 虽然看起来全部是Go语言代码,但是执行的时候是先从Go语言的`main`函数,到CGO自动生成的C语言版本`SayHello`桥接函数,最后又回到了Go语言环境的`SayHello`函数。虽然看起来有点绕,但CGO确实是这样运行的。 + 需要注意的是,CGO导出Go语言函数时,函数参数中不再支持C语言中`const`修饰符。 diff --git a/ch2-cgo/ch2-02-cgo-types.md b/ch2-cgo/ch2-02-cgo-types.md index 9962e35..ee55f4d 100644 --- a/ch2-cgo/ch2-02-cgo-types.md +++ b/ch2-cgo/ch2-02-cgo-types.md @@ -90,7 +90,22 @@ extern void helloString(GoString p0); extern void helloSlice(GoSlice p0); ``` -不过需要注意的是,如果使用了GoString类型则会对`_cgo_export.h`头文件产生依赖,而这个头文件是动态输出的。更严谨的做法是为C语言函数接口定义严格的头文件,然后基于稳定的头文件实现代码。 +不过需要注意的是,如果使用了GoString类型则会对`_cgo_export.h`头文件产生依赖,而这个头文件是动态输出的。 + +Go1.10针对Go字符串增加了一个`_GoString_`预定义类型,可以降低在cgo代码中可能对`_cgo_export.h`头文件产生的循环依赖的风险。我们可以调整helloString函数的C语言声明为: + +```c +extern void helloString(_GoString_ p0); +``` + +因为`_GoString_`是预定义类型,我们无法通过此类型直接访问字符串的长度和指针等信息。Go1.10同时也增加了以下两个函数用于获取字符串结构中的长度和指针信息: + +```c +size_t _GoStringLen(_GoString_ s); +const char *_GoStringPtr(_GoString_ s); +``` + +更严谨的做法是为C语言函数接口定义严格的头文件,然后基于稳定的头文件实现代码。 ## 结构体、联合、枚举类型 @@ -341,17 +356,23 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; 在C语言中可以通过`GoString`和`GoSlice`来访问Go语言的字符串和切片。如果是Go语言中数组类型,可以将数组转为切片后再行转换。如果字符串或切片对应的底层内存空间由Go语言的运行时管理,那么在C语言中不能长时间保存Go内存对象。 + 关于CGO内存模型的细节在稍后章节中会详细讨论。 -## 指针和切片 +## 指针间的转换 -C语言和Go语言指针的转换可以看做是两种不同类型的指针之间的转换。在Go语言中我们无法在不同类型之间做转换,因此不同类型的指针之间也存在此限制。由于任意类型的指针均可和`unsafe.Pointer`相互转换,所以我们可以以`unsafe.Pointer`作为中间桥接类型实现不同类型指针之间的转换。 +在C语言中,不同类型的指针是可以显式或隐式转换的,如果是隐式只是会在编译时给出一些警告信息。但是Go语言对于不同类型的转换非常严格,任何C语言中可能出现的警告信息在Go语言中都可能是错误!指针是C语言的灵魂,指针间的自由转换也是cgo代码中经常要解决的第一个重要的问题。 + +在Go语言中两个指针的类型完全一致则不需要转换可以直接通用。如果一个指针类型是用type命令在另一个指针类型基础之上构建的,换言之两个指针底层是相同完全结构的指针,那么我我们可以通过直接强制转换语法进行指针间的转换。但是cgo经常要面对的是2个完全不同类型的指针间的转换,原则上这种操作在纯Go语言代码是严格禁止的。 + +cgo存在的一个目的就是打破Go语言的禁制,恢复C语言应有的指针的自由转换和指针运算。以下代码演示了如何将X类型的指针转化为Y类型的指针: ```go var p *X @@ -361,7 +382,31 @@ q = (*Y)(unsafe.Pointer(p)) // *X => *Y p = (*X)(unsafe.Pointer(q)) // *Y => *X ``` -再结合`reflect.SliceHeader`类型,我们可以实现`[]X`和`[]Y`类型的切片转换: +为了实现X类型指针到Y类型指针的转换,我们需要借助`unsafe.Pointer`作为中间桥接类型实现不同类型指针之间的转换。`unsafe.Pointer`指针类型类似C语言中的`void*`类型的指针。 + +下面是指针间的转换流程的示意图: + +![](../images/ch2-x-ptr-to-y-ptr.uml.png) + +任何类型的指针都可以通过强制转换为`unsafe.Pointer`指针类型去掉原有的类型信息,然后再重新赋予新的指针类型而达到指针间的转换的目的。 + +## 数值和指针的转换 + +不同类型指针间的转换看似复杂,但是在cgo中已经算是比较简单的了。在C语言中经常遇到用普通数值表示指针的场景,也就是说如何实现数值和指针的转换也是cgo需要面对的一个问题。 + +为了严格控制指针的使用,Go语言禁止将数值类型直接转为指针类型!不过,Go语言针对`unsafe.Pointr`指针类型特别定义了一个uintptr类型。我们可以uintptr为中介,实现数值类型到`unsafe.Pointr`指针类型到转换。再结合前面提到的方法,就可以实现数值和指针的转换了。 + +下面流程图演示了如何实现int32类型到C语言的`char*`字符串指针类型的相互转换: + +![](../images/ch2-int32-to-char-ptr.uml.png) + +转换分为几个阶段,在每个阶段实现一个小目标:首先是int32到uintptr类型,然后是uintptr到`unsafe.Pointr`指针类型,最后是`unsafe.Pointr`指针类型到`*C.char`类型。 + +## 切片间的转换 + +在C语言中数组也一种指针,因此两个不同类型数组之间到转换和指针间转换基本类似。但是在Go语言中,数组或数组对应到切片都不再是指针类型,因为我们也就无法直接实现不同类型到切片之间的转换。 + +不过Go语言的reflect包提供了切片类型到底层结构,再结合前面讨论到不同类型之间到指针转换技术就可以实现`[]X`和`[]Y`类型的切片转换: ```go var p []X @@ -375,6 +420,10 @@ pHdr.Len = qHdr.Len * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0]) pHdr.Cap = qHdr.Cap * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0]) ``` -如果X和Y类型的大小不同,需要重新设置Len和Cap属性。需要注意的是,如果X或Y是空类型,上述代码中可能导致除0错误,实际代码需要根据情况酌情处理。 +不同切片类型之间转换到思路是先为构造一个空的目标切片,然后用原有的切片底层数据填充目标切片。如果X和Y类型的大小不同,需要重新设置Len和Cap属性。需要注意的是,如果X或Y是空类型,上述代码中可能导致除0错误,实际代码需要根据情况酌情处理。 + +下面演示了切片间的转换的具体流程: + +![](../images/ch2-x-slice-to-y-slice.uml.png) 针对CGO中常用的功能,作者封装了 "github.com/chai2010/cgo" 包,提供基本的转换功能,具体的细节可以参考实现代码。 diff --git a/ch2-cgo/ch2-03-basic.md b/ch2-cgo/ch2-03-basic.md index ca77baa..503a4a2 100644 --- a/ch2-cgo/ch2-03-basic.md +++ b/ch2-cgo/ch2-03-basic.md @@ -169,3 +169,13 @@ go build -tags="windows,debug" ``` 其中`linux,386`中linux和386用逗号链接表示AND的意思;而`linux,386`和`darwin,!cgo`之间通过空白分割来表示OR的意思。 + +## CGO生成的中间文件 + +要了解CGO技术的底层秘密首先需要了解CGO生成了哪些中间文件。我们可以在构建一个cgo包时增加一个`-work`输出中间生成文件所在的目录并且在构建完成时保留中间文件。如果是比较简单的cgo代码我们也可以直接通过手工调用`go tool cgo`命令来查看生成的中间文件。 + +在一个Go源文件中,如果出现了`import "C"`指令则表示将调用cgo命令生成对应的中间文件。下图是cgo生成的中间文件的简单示意图: + +![](../images/ch2-cgo-generated-files.dot.png) + +包中有4个Go文件,其中nocgo开头的文件中没有`import "C"`指令,其它的2个文件则包含了cgo代码。cgo命令会为每个包含了cgo代码的Go文件创建2个中间文件,比如 main.go 会分别创建 main.cgo1.go 和 main.cgo2.c 两个中间文件。然后会为整个包创建一个 `_cgo_gotypes.go` Go文件,其中包含Go语言部分辅助代码。此外还会创建一个 `_cgo_export.h` 和 `_cgo_export.c` 文件,对应Go语言导出到C语言的类型和函数。 diff --git a/ch2-cgo/ch2-06-static-shared-lib.md b/ch2-cgo/ch2-06-static-shared-lib.md index 0eb1752..4623f5f 100644 --- a/ch2-cgo/ch2-06-static-shared-lib.md +++ b/ch2-cgo/ch2-06-static-shared-lib.md @@ -1,10 +1,10 @@ # 2.6. 静态库和动态库 -CGO在使用C/C++资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在`import "C"`之前的注释部分包含C代码,或者在当前包中包含C/C++源文件。链接静态库和动态库的方式比较类似,都是通过在LDFLAGS选项指定要链接的库方式链接。本节我们主要关注CGO中如何处理静态库和动态库相关的问题。 +CGO在使用C/C++资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在`import "C"`之前的注释部分包含C代码,或者在当前包中包含C/C++源文件。链接静态库和动态库的方式比较类似,都是通过在LDFLAGS选项指定要链接的库方式链接。本节我们主要关注在CGO中如何使用静态库和动态库相关的问题。 ## 使用C静态库 -如果CGO中引入的C/C++资源有代码而且代码规模也比较小,直接使用源码是最理想的方式。很多时候我们并没有源代码,或者从C/C++源代码的构建过程异常复杂,这种时候使用C静态库也是一个不错的选择。静态库因为是静态链接,最终的目标程序并不会产生额外的运行时依赖,而且也不会出现动态库特有的跨运行时资源管理的错误。不过静态库对链接阶段会有一定要求:静态库一般包含了全部的代码,里面会有大量的符号,如果不同静态库之间出现符号冲突会导致链接的失败。 +如果CGO中引入的C/C++资源有代码而且代码规模也比较小,直接使用源码是最理想的方式,但很多时候我们并没有源代码,或者从C/C++源代码开始构建的过程异常复杂,这种时候使用C静态库也是一个不错的选择。静态库因为是静态链接,最终的目标程序并不会产生额外的运行时依赖,也不会出现动态库特有的跨运行时资源管理的错误。不过静态库对链接阶段会有一定要求:静态库一般包含了全部的代码,里面会有大量的符号,如果不同静态库之间出现了符号冲突则会导致链接的失败。 我们先用纯C语言构造一个简单的静态库。我们要构造的静态库名叫number,库中只有一个number_add_mod函数,用于表示数论中的模加法运算。number库的文件都在number目录下。 @@ -26,7 +26,7 @@ int number_add_mod(int a, int b, int mod) { 因为CGO使用的是GCC命令来编译和链接C和Go桥接的代码。因此静态库也必须是GCC兼容的格式。 -通过以下命令可以生成一个libnumber.a的静态库: +通过以下命令可以生成一个叫libnumber.a的静态库: ``` $ cd ./number @@ -53,11 +53,11 @@ func main() { } ``` -其中有两个#cgo命令,分别是编译和链接参数。CFLAGS通过`-I./number`将number库对应头文件所在的目录加入头文件检索路径。LDFLAGS通过`-L${SRCDIR}/number`将编译后number静态库所在目录加为链接库检索路径,`-lnumber`表示链接libnumber.a静态库。需要注意的是,在链接部分的检索路径不能使用相对路径(这是由于C/C++代码的链接程序所限制),我们必须通过cgo特有的`${SRCDIR}`变量将源文件对应的当前目录路径展开为绝对路径(因此在windows平台中绝对路径不能有空白符号)。 +其中有两个#cgo命令,分别是编译和链接参数。CFLAGS通过`-I./number`将number库对应头文件所在的目录加入头文件检索路径。LDFLAGS通过`-L${SRCDIR}/number`将编译后number静态库所在目录加为链接库检索路径,`-lnumber`表示链接libnumber.a静态库。需要注意的是,在链接部分的检索路径不能使用相对路径(C/C++代码的链接程序所限制),我们必须通过cgo特有的`${SRCDIR}`变量将源文件对应的当前目录路径展开为绝对路径(因此在windows平台中绝对路径不能有空白符号)。 -因为我们有number库的全部代码,我们可以用go generate工具来生成静态库,或者是通过Makefile来构建静态库。因此发布CGO源码包时,我们并不需要提前构建C静态库。 +因为我们有number库的全部代码,所以我们可以用go generate工具来生成静态库,或者是通过Makefile来构建静态库。因此发布CGO源码包时,我们并不需要提前构建C静态库。 -因为多了一个静态库的构建步骤,这种使用了自定义静态库并已经包含了静态库全部代码的Go包无法直接用go get安装。不过我们依然可以通过go get下载,然后用go generate触发静态库构建,最后才是go install安装来完成。 +因为多了一个静态库的构建步骤,这种使用了自定义静态库并已经包含了静态库全部代码的Go包无法直接用go get安装。不过我们依然可以通过go get下载,然后用go generate触发静态库构建,最后才是go install来完成安装。 为了支持go get命令直接下载并安装,我们C语言的`#include`语法可以将number库的源文件链接到当前的包。 @@ -71,11 +71,11 @@ func main() { 如果使用的是第三方的静态库,我们需要先下载安装静态库到合适的位置。然后在#cgo命令中通过CFLAGS和LDFLAGS来指定头文件和库的位置。对于不同的操作系统甚至同一种操作系统的不同版本来说,这些库的安装路径可能都是不同的,那么如何在代码中指定这些可能变化的参数呢? -在Linux环境,有个一个pkg-config命令可以查询要使用某个静态库或动态库时的编译和链接参数。我们可以在#cgo命令中直接使用pkg-config命令来生成编译和链接参数。而且还可以通过PKG_CONFIG环境变量订制pkg-config命令。因为不同的操作系统对pkg-config命令的支持不尽相同,通过该方式很难兼容不同的操作系统下的构建参数。不过对于Linux等特定的系统,pkg-config命令确实可以简化构建参数的管理。关于pkg-config的使用细节我们不再深入展开,大家可以自行参考相关文档。 +在Linux环境,有一个pkg-config命令可以查询要使用某个静态库或动态库时的编译和链接参数。我们可以在#cgo命令中直接使用pkg-config命令来生成编译和链接参数。而且还可以通过PKG_CONFIG环境变量订制pkg-config命令。因为不同的操作系统对pkg-config命令的支持不尽相同,通过该方式很难兼容不同的操作系统下的构建参数。不过对于Linux等特定的系统,pkg-config命令确实可以简化构建参数的管理。关于pkg-config的使用细节在此我们不深入展开,大家可以自行参考相关文档。 ## 使用C动态库 -动态库出现的初衷是对于相同的库,多个进程可以共享一个动态库,可以节省内存和磁盘资源。但是在磁盘和内存已经白菜价的今天,动态库能节省的空间已经微不足道了,那么动态库还有哪些存在的价值呢?从库开发角度来说,动态库可以隔离不同动态库之间的关系,减少链接时出现符号冲突的风险。而且对于windows等平台,动态库是跨越VC和GCC不太编译器平台的唯一的可行方式。 +动态库出现的初衷是对于相同的库,多个进程可以共享同一个,以节省内存和磁盘资源。但是在磁盘和内存已经白菜价的今天,这两个作用已经显得微不足道了,那么除此之外动态库还有哪些存在的价值呢?从库开发角度来说,动态库可以隔离不同动态库之间的关系,减少链接时出现符号冲突的风险。而且对于windows等平台,动态库是跨越VC和GCC不同编译器平台的唯一的可行方式。 对于CGO来说,使用动态库和静态库是一样的,因为动态库也必须要有一个小的静态导出库用于链接动态库(Linux下可以直接链接so文件,但是在Windows下必须为dll创建一个`.a`文件用于链接)。我们还是以前面的number库为例来说明如何以动态库方式使用。 @@ -118,7 +118,7 @@ number_add_mod 其中第一行的LIBRARY指明动态库的文件名,然后的EXPORTS语句之后是要导出的符号名列表。 -现在我们可以用以下命令来创建动态库(需要进入VC对于的x64命令行环境)。 +现在我们可以用以下命令来创建动态库(需要进入VC对应的x64命令行环境)。 ``` $ cl /c number.c @@ -139,7 +139,7 @@ $ dlltool -dllname number.dll --def number.def --output-lib libnumber.a ## 导出C静态库 -CGO不仅可以使用C静态库,也可以将Go实现的函数导出为C静态库。我们现在用Go实现前面的number库的莫加法函数。 +CGO不仅可以使用C静态库,也可以将Go实现的函数导出为C静态库。我们现在用Go实现前面的number库的模加法函数。 创建number.go,内容如下: @@ -180,7 +180,7 @@ extern int number_add_mod(int p0, int p1, int p2); 其中`extern "C"`部分的语法是为了同时适配C和C++两种语言。核心内容是声明了要导出的number_add_mod函数。 -然后我们创建一个`_test_main.c`的C文件用于测试生成的C静态库(用下划线作为前著名是让go build构建C静态库时忽略这个文件): +然后我们创建一个`_test_main.c`的C文件用于测试生成的C静态库(用下划线作为前缀名是让为了让go build构建C静态库时忽略这个文件): ```c #include "number.h" @@ -241,11 +241,11 @@ $ ./a.out Requires exactly one main package to be listed. ``` -文档说明导出的C函数必须是在main包导出,然后才能在生成的头文件包含声明的语句。但是很多时候我们可能更希望将不太类型的导出函数组织到不同的Go包中,然后统一导出为一个静态库或动态库。 +文档说明导出的C函数必须是在main包导出,然后才能在生成的头文件包含声明的语句。但是很多时候我们可能更希望将不同类型的导出函数组织到不同的Go包中,然后统一导出为一个静态库或动态库。 要实现从是从非main包导出C函数,或者是多个包导出C函数(因为只能有一个main包),我们需要自己提供导出C函数对应的头文件(因为CGO无法为非main包的导出函数生成头文件)。 -假设我们先创建一个number子包,用于提供莫加法函数: +假设我们先创建一个number子包,用于提供模加法函数: ```go package number diff --git a/ch2-cgo/ch2-07-py-module.md b/ch2-cgo/ch2-07-py-module.md index 086c1c3..1b1b55c 100644 --- a/ch2-cgo/ch2-07-py-module.md +++ b/ch2-cgo/ch2-07-py-module.md @@ -1,11 +1,158 @@ -# 2.7 Go实现Python模块(TODO) +# 2.7 Go实现Python模块 -TODO +前面章节我们已经讲述了如何通过CGO来引用和创建C动态库和静态库。实现了对C动态库和静态库的支持,理论上就可以应用到动态库的绝大部分场景。Python语言作为当下最红的语言,本节我们将演示如何通过Go语言来为Python脚本语言编写扩展模块。 - +我们使用的是之前出现过的例子: + +```go +// main.go +package main + +import "C" +import "fmt" + +func main() {} + +//export SayHello +func SayHello(name *C.char) { + fmt.Printf("hello %s!\n", C.GoString(name)) +} +``` + +其中只导出了一个SayHello函数,用于打印字符串。通过以下命令基于上述Go代码创建say-hello.so动态库: + +``` +go build -buildmode=c-shared -o say-hello.so main.go +``` + +现在我们就可以通过ctypes模块调用say-hello.so动态库中的SayHello函数了: + +```python +// hello.py +import ctypes + +libso = ctypes.CDLL("./say-hello.so") + +SayHello = libso.SayHello +SayHello.argtypes = [ctypes.c_char_p] +SayHello.restype = None + +SayHello(ctypes.c_char_p(b"hello")) +``` + +我们首先通过ctypes.CDLL加载动态库到libso,并通过libso.SayHello来获取SayHello函数。获取到SayHello函数之后设置函数的输入参数为一个C语言类型的字符串,该函数没有返回值。然后我们通过`ctypes.c_char_p(b"hello")`将Python字节串转为C语言格式的字符串作为参数调用SayHello。如果一切正常的话就可以输出字符串了。 + +从这个例子可以看出,给予ctypes构造Python扩展模块非常简单,本质上只是在构建一个纯C语言规格的动态库。比较复杂的部分在ctypes的具体使用,关于ctypes的具体细节就不详细展开的,用户可以自行参考Python自带的官方文档。 + +## 基于Python C接口创建 + +在前面的例子中,通过ctypes创建的模块必须要用Python再包装一层,否则就要直接面对C语言风格的接口。如果基于基于Python C接口,我们可以完全再Go和C语言层面创建灵活强大的模块,重点是不再需要在Python中重新包装。 + +基于Python C接口创建模块和使用C语言的静态库的流程类似: + +``` +package main + +/* +// macOS: +#cgo darwin pkg-config: python3 + +// linux +#cgo linux pkg-config: python3 + +// windows +// should generate libpython3.a from python3.lib + +#define Py_LIMITED_API +#include + +extern PyObject* PyInit_gopkg(); +extern PyObject* Py_gopkg_sum(PyObject *, PyObject *); + +static int cgo_PyArg_ParseTuple_ii(PyObject *arg, int *a, int *b) { + return PyArg_ParseTuple(arg, "ii", a, b); +} + +static PyObject* cgo_PyInit_gopkg(void) { + static PyMethodDef methods[] = { + {"sum", Py_gopkg_sum, METH_VARARGS, "Add two numbers."}, + {NULL, NULL, 0, NULL}, + }; + static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, methods, + }; + return PyModule_Create(&module); +} +*/ +import "C" + +func main() {} + +//export PyInit_gopkg +func PyInit_gopkg() *C.PyObject { + return C.cgo_PyInit_gopkg() +} + +//export Py_gopkg_sum +func Py_gopkg_sum(self, args *C.PyObject) *C.PyObject { + var a, b C.int + if C.cgo_PyArg_ParseTuple_ii(args, &a, &b) == 0 { + return nil + } + return C.PyLong_FromLong(C.long(a + b)) +} +``` + +因为Python的链接参数要复杂了很多,我们借助pkg-config工具来获取编译参数和链接参数。然后我们在Go语言中分别导出了PyInit_gopkg和Py_gopkg_sum函数,其中PyInit_gopkg函数用于初始化名为gopkg的Python模块,而Py_gopkg_sum函数则是模块中sum方法的实现。 + +因此PyArg_ParseTuple是可变参数类型,CGO中无法使用可变参数的C函数,因此我们通过增加一个cgo_PyArg_ParseTuple_ii辅助函数小消除可变参数的影响。同样,模块的方法列表必须在C语言内存空间创建,因为CGO是禁止将Go语言内存直接返回到C语言空间的。 + +然后通过以下命令创建gopkg.so动态库: + +``` +go build -buildmode=c-shared -o gopkg.so main.go +``` + +这里需要注意几个出现gopkg名字的地方。gopkg是我们创建的Python模块的名字,因此它对应一个gopkg.so动态库。再gopkg.so动态库中必须有一个PyInit_gopkg函数,该函数是模块的初始化函数。在PyInit_gopkg函数初始化模块时,同样需要指定模块的名字时gopkg。模块中的方法函数是通过函数指针访问,具体的名字没有影响。 + +### macOS环境构建 + +因为在macOS中,pkg-config不支持Python3版本。不过macOS有一个python3-config的命令可以实现pkg-config类似的功能。不过python3-config生成的编译参数无法直接用于CGO编译选项(因为GCC不能识别部分参数会导致错误构建)。 + +我们在python3-config的基础只是又包装了一个工具,在通过python3-config获取到编译参数之后将GCC不支持的参数剔除掉。 + +创建py3-config.go文件: + +```go +func main() { + for _, s := range os.Args { + if s == "--cflags" { + out, _ := exec.Command("python3-config", "--cflags").CombinedOutput() + out = bytes.Replace(out, []byte("-arch"), []byte{}, -1) + out = bytes.Replace(out, []byte("i386"), []byte{}, -1) + out = bytes.Replace(out, []byte("x86_64"), []byte{}, -1) + fmt.Print(string(out)) + return + } + if s == "--libs" { + out, _ := exec.Command("python3-config", "--ldflags").CombinedOutput() + fmt.Print(string(out)) + return + } + } +} +``` + +cgo中的pkg-config只需要两个参数`--cflags`和`--libs`。其中`--libs`选项的输出我们采用的是`python3-config --ldflags`的输出,因为`--libs`选项没有包含库的检索路径,而`--ldflags`选项则是在指定链接库参数的基础上增加了库的检索路径。 + +基于py3-config.go可以创建一个py3-config命令。然后通过PKG_CONFIG环境变量将cgo使用的pkg-config命令指定为我们订制的命令: + +``` +PKG_CONFIG=./py3-config go build -buildmode=c-shared -o gopkg.so main.go +``` + +对于不支持pkg-config的平台我们都可以基于类似的方法处理。 diff --git a/ch2-cgo/ch2-08-faq.md b/ch2-cgo/ch2-08-faq.md new file mode 100644 index 0000000..5f26326 --- /dev/null +++ b/ch2-cgo/ch2-08-faq.md @@ -0,0 +1,6 @@ +# 2.8. 补充说明 + +为何要话费巨大的精力学习CGO是一个问题。任何技术和语言都有它自身的优点和不足,Go语言不是银弹,它无法解决全部问题。而通过CGO可以继承C/C++将近半个世纪的软件遗产,通过CGO可以用Go给其它系统写C接口的共享库,通过CGO技术可以让Go语言编写的代码可以很好地融入现有的软件生态——而现在的软件正式建立在C/C++语言之上的。因此说CGO是一个保底的后备技术,它是Go的一个重量级的替补技术,值得任何一个严肃的Go语言开发人员学习。 + +本章讨论了CGO的一些常见用法,并给出相关的例子。关于CGO有几点补充:如果有纯Go的解决方法就不要使用CGO;CGO中涉及的C和C++构建问题非常繁琐;CGO有一定的限制无法实现解决全部的问题;不要试图越过CGO的一些限制。而且CGO只是一种官方提供并推荐的Go语言和C/C++交互的方法。如果是使用的gccgo的版本,可以通过gccgo的方式实现Go和C/C++的交互。同时SWIG也是一种选择,并对C++诸多特性提供了支持。 + diff --git a/ch2-cgo/ch2-08-swig.md b/ch2-cgo/ch2-08-swig.md deleted file mode 100644 index fa74b72..0000000 --- a/ch2-cgo/ch2-08-swig.md +++ /dev/null @@ -1,7 +0,0 @@ -# 2.8. SWIG(TODO) - -TODO - - diff --git a/ch2-cgo/ch2-09-faq.md b/ch2-cgo/ch2-09-faq.md deleted file mode 100644 index 264511a..0000000 --- a/ch2-cgo/ch2-09-faq.md +++ /dev/null @@ -1,11 +0,0 @@ -# 2.9. 补充说明(TODO) - -TODO - - diff --git a/ch3-asm/ch3-01-basic.md b/ch3-asm/ch3-01-basic.md new file mode 100644 index 0000000..a04ee5a --- /dev/null +++ b/ch3-asm/ch3-01-basic.md @@ -0,0 +1,3 @@ +# 3.1. 汇编基础(TODO) + +TODO diff --git a/ch3-asm/ch3-02-control-flow.md b/ch3-asm/ch3-02-control-flow.md new file mode 100644 index 0000000..9278cc2 --- /dev/null +++ b/ch3-asm/ch3-02-control-flow.md @@ -0,0 +1,3 @@ +# 3.2. 控制流(TODO) + +TODO diff --git a/ch3-asm/ch3-03-var.md b/ch3-asm/ch3-03-var.md new file mode 100644 index 0000000..3e6272e --- /dev/null +++ b/ch3-asm/ch3-03-var.md @@ -0,0 +1,3 @@ +# 3.3. 包变量(TODO) + +TODO diff --git a/ch3-asm/ch3-04-leaf-func.md b/ch3-asm/ch3-04-leaf-func.md new file mode 100644 index 0000000..67c3d1a --- /dev/null +++ b/ch3-asm/ch3-04-leaf-func.md @@ -0,0 +1,3 @@ +# 3.4. 叶子函数(TODO) + +TODO diff --git a/ch3-asm/ch3-05-more-stack.md b/ch3-asm/ch3-05-more-stack.md new file mode 100644 index 0000000..b980e66 --- /dev/null +++ b/ch3-asm/ch3-05-more-stack.md @@ -0,0 +1,3 @@ +# 3.5. 动态栈(TODO) + +TODO diff --git a/ch3-asm/ch3-06-gc.md b/ch3-asm/ch3-06-gc.md new file mode 100644 index 0000000..6b7748a --- /dev/null +++ b/ch3-asm/ch3-06-gc.md @@ -0,0 +1,3 @@ +# 3.6. 垃圾回收(TODO) + +TODO diff --git a/ch3-asm/ch3-07-pyrdown.md b/ch3-asm/ch3-07-pyrdown.md new file mode 100644 index 0000000..4cde048 --- /dev/null +++ b/ch3-asm/ch3-07-pyrdown.md @@ -0,0 +1,3 @@ +# 3.7. 例子: 图像降采样(TODO) + +TODO diff --git a/ch3-asm/ch3-08-faq.md b/ch3-asm/ch3-08-faq.md new file mode 100644 index 0000000..f89b61a --- /dev/null +++ b/ch3-asm/ch3-08-faq.md @@ -0,0 +1,3 @@ +# 3.8. 补充说明(TODO) + +TODO diff --git a/ch3-asm/readme.md b/ch3-asm/readme.md index 20f06e0..0734dba 100644 --- a/ch3-asm/readme.md +++ b/ch3-asm/readme.md @@ -1,3 +1,3 @@ -# 第三章 Go汇编语言 +# 第三章 Go汇编语言(TODO) TODO diff --git a/ch6-web/ch6-03-middleware.md b/ch6-web/ch6-03-middleware.md index 85481e6..ef6acbe 100644 --- a/ch6-web/ch6-03-middleware.md +++ b/ch6-web/ch6-03-middleware.md @@ -153,12 +153,26 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) func (ResponseWriter, *Request) ``` -那么这个 handler 就是一个 HandlerFunc 类型了,也就相当于实现了 http.Handler 这个接口,这要归功于 golang 的 duck typing 式的类型系统。在 http 库需要调用你的 handler 函数来处理 http 请求时,会调用 HandlerFunc 的 ServeHTTP 函数,可见一个请求的基本调用链是这样的: +那么这个 handler 和 http.HandlerFunc 就有了一致的函数签名,可以将该 handler 函数进行类型转换,转为 http.HandlerFunc。而 http.HandlerFunc 实现了 http.Handler 这个接口。在 http 库需要调用你的 handler 函数来处理 http 请求时,会调用 HandlerFunc 的 ServeHTTP 函数,可见一个请求的基本调用链是这样的: ```go h = getHandler() => h.ServeHTTP(w, r) => h(w, r) ``` +上面提到的把自定义 handler 转换为 http.HandlerFunc 这个过程是必须的,因为我们的 handler 没有直接实现 ServeHTTP 这个接口。上面的代码中我们看到的 HandleFunc(注意 HandlerFunc 和 HandleFunc 的区别)里也可以看到这个强制转换过程: + +```go +func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { + DefaultServeMux.HandleFunc(pattern, handler) +} + +// 调用 + +func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { + mux.Handle(pattern, HandlerFunc(handler)) +} +``` + 知道 handler 是怎么一回事,我们的中间件通过包装 handler,再返回一个新的 handler 就好理解了。 总结一下,我们的中间件要做的事情就是通过一个或多个函数对 handler 进行包装,返回一个包括了各个中间件逻辑的函数链。我们把上面的包装再做得复杂一些: diff --git a/examples/ch2-01/hello-06/main.go b/examples/ch2-01/hello-06/main.go new file mode 100644 index 0000000..0f0f8f8 --- /dev/null +++ b/examples/ch2-01/hello-06/main.go @@ -0,0 +1,24 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build go1.10 + +package main + +//void SayHello(_GoString_ s); +import "C" + +import ( + "fmt" +) + +func main() { + C.SayHello("Hello, World\n") + C.SayHello("") +} + +//export SayHello +func SayHello(s string) { + fmt.Println(int(C._GoStringLen(s))) + fmt.Print(s) +} diff --git a/examples/ch2-07/hello-py/Makefile b/examples/ch2-07/hello-py/Makefile index bb54aa2..d224e8d 100644 --- a/examples/ch2-07/hello-py/Makefile +++ b/examples/ch2-07/hello-py/Makefile @@ -2,14 +2,10 @@ # License: https://creativecommons.org/licenses/by-nc-sa/4.0/ default: - go build -buildmode=c-shared -o gopkg.so main.go - python3 -c 'import gopkg; print(gopkg.system("time"))' - python3 -c 'import gopkg; print(gopkg.sum(2, 3))' + go build -o py3-config.out py3-config.go + PKG_CONFIG=./py3-config.out go build -buildmode=c-shared -o gopkg.so main.go + -rm py3-config.out + python3 -c 'import gopkg; print(gopkg.sum(1, 2))' clean: -rm *.so - -# PKG_CONFIG=python3-config go build -buildmode=c-shared -o say-hello.so main.go -# python3-config --ldflags -# python3-config --include - diff --git a/examples/ch2-07/hello-py/gopkg.h b/examples/ch2-07/hello-py/gopkg.h index a5f4a5a..68ac6e6 100644 --- a/examples/ch2-07/hello-py/gopkg.h +++ b/examples/ch2-07/hello-py/gopkg.h @@ -5,12 +5,9 @@ /* Start of preamble from import "C" comments. */ -#line 3 "/Users/chai/go/src/github.com/chai2010/advanced-go-programming-book/examples/ch2-07/hello-py/main.go" +#line 6 "/Users/chai/go/src/github.com/chai2010/advanced-go-programming-book/examples/ch2-07/hello-py/main.go" // macOS: -// python3-config --cflags -// python3-config --ldflags - // linux @@ -22,30 +19,22 @@ #define Py_LIMITED_API #include -static PyObject * -spam_system(PyObject *self, PyObject *args) { - const char *command; - if (!PyArg_ParseTuple(args, "s", &command)) { - return NULL; - } +extern PyObject* PyInit_gopkg(); +extern PyObject* Py_gopkg_sum(PyObject *, PyObject *); - int status = system(command); - return PyLong_FromLong(status); +static int cgo_PyArg_ParseTuple_ii(PyObject *arg, int *a, int *b) { + return PyArg_ParseTuple(arg, "ii", a, b); } -extern PyObject *sum(PyObject *self, PyObject *args); - -static PyMethodDef modMethods[] = { - {"system", spam_system, METH_VARARGS, "Execute a shell command."}, - {"sum", sum, METH_VARARGS, "Execute a shell command."}, - {NULL, NULL, 0, NULL} -}; - -static PyObject* PyInit_gopkg_(void) { - static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, modMethods, +static PyObject* cgo_PyInit_gopkg(void) { + static PyMethodDef methods[] = { + {"sum", Py_gopkg_sum, METH_VARARGS, "Add two numbers."}, + {NULL, NULL, 0, NULL}, }; - return (void*)PyModule_Create(&module); + static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, methods, + }; + return PyModule_Create(&module); } #line 1 "cgo-generated-wrapper" @@ -97,10 +86,10 @@ extern "C" { #endif -extern PyObject* sum(PyObject* p0, PyObject* p1); - extern PyObject* PyInit_gopkg(); +extern PyObject* Py_gopkg_sum(PyObject* p0, PyObject* p1); + #ifdef __cplusplus } #endif diff --git a/examples/ch2-07/hello-py/main.go b/examples/ch2-07/hello-py/main.go index 81c3d3a..9262704 100644 --- a/examples/ch2-07/hello-py/main.go +++ b/examples/ch2-07/hello-py/main.go @@ -5,10 +5,7 @@ package main /* // macOS: -// python3-config --cflags -// python3-config --ldflags -#cgo darwin CFLAGS: -I/Library/Frameworks/Python.framework/Versions/3.6/include/python3.6m -I/Library/Frameworks/Python.framework/Versions/3.6/include/python3.6m -fno-strict-aliasing -Wsign-compare -fno-common -dynamic -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -g -#cgo darwin LDFLAGS: -L/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/config-3.6m-darwin -lpython3.6m -ldl -framework CoreFoundation +#cgo darwin pkg-config: python3 // linux #cgo linux pkg-config: python3 @@ -19,51 +16,38 @@ package main #define Py_LIMITED_API #include -static PyObject * -spam_system(PyObject *self, PyObject *args) { - const char *command; - if (!PyArg_ParseTuple(args, "s", &command)) { - return NULL; - } +extern PyObject* PyInit_gopkg(); +extern PyObject* Py_gopkg_sum(PyObject *, PyObject *); - int status = system(command); - return PyLong_FromLong(status); +static int cgo_PyArg_ParseTuple_ii(PyObject *arg, int *a, int *b) { + return PyArg_ParseTuple(arg, "ii", a, b); } -extern PyObject *sum(PyObject *self, PyObject *args); - -static PyMethodDef modMethods[] = { - {"system", spam_system, METH_VARARGS, "Execute a shell command."}, - {"sum", sum, METH_VARARGS, "Execute a shell command."}, - {NULL, NULL, 0, NULL} -}; - -static PyObject* PyInit_gopkg_(void) { - static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, modMethods, +static PyObject* cgo_PyInit_gopkg(void) { + static PyMethodDef methods[] = { + {"sum", Py_gopkg_sum, METH_VARARGS, "Add two numbers."}, + {NULL, NULL, 0, NULL}, }; - return (void*)PyModule_Create(&module); + static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, methods, + }; + return PyModule_Create(&module); } */ import "C" -import ( - "fmt" -) - func main() {} -// export SayHello -func SayHello(name *C.char) { - fmt.Printf("hello %s!\n", C.GoString(name)) -} - -//export sum -func sum(self, args *C.PyObject) *C.PyObject { - return C.PyLong_FromLongLong(9527) // TODO -} - //export PyInit_gopkg func PyInit_gopkg() *C.PyObject { - return C.PyInit_gopkg_() + return C.cgo_PyInit_gopkg() +} + +//export Py_gopkg_sum +func Py_gopkg_sum(self, args *C.PyObject) *C.PyObject { + var a, b C.int + if C.cgo_PyArg_ParseTuple_ii(args, &a, &b) == 0 { + return nil + } + return C.PyLong_FromLong(C.long(a + b)) } diff --git a/examples/ch2-07/hello-py/py3-config.go b/examples/ch2-07/hello-py/py3-config.go new file mode 100644 index 0000000..64cdbbe --- /dev/null +++ b/examples/ch2-07/hello-py/py3-config.go @@ -0,0 +1,34 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build ignore +// +build darwin + +// fix python3-config for cgo build + +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" +) + +func main() { + for _, s := range os.Args { + if s == "--cflags" { + out, _ := exec.Command("python3-config", "--cflags").CombinedOutput() + out = bytes.Replace(out, []byte("-arch"), []byte{}, -1) + out = bytes.Replace(out, []byte("i386"), []byte{}, -1) + out = bytes.Replace(out, []byte("x86_64"), []byte{}, -1) + fmt.Print(string(out)) + return + } + if s == "--libs" { + out, _ := exec.Command("python3-config", "--ldflags").CombinedOutput() + fmt.Print(string(out)) + return + } + } +} diff --git a/examples/ch2-07/hello-so/hello.py b/examples/ch2-07/hello-so/hello.py index cf99d7b..5cd267f 100644 --- a/examples/ch2-07/hello-so/hello.py +++ b/examples/ch2-07/hello-so/hello.py @@ -1,12 +1,12 @@ # Copyright © 2017 ChaiShushan . # License: https://creativecommons.org/licenses/by-nc-sa/4.0/ -from ctypes import * +import ctypes -libso = CDLL("./say-hello.so") +libso = ctypes.CDLL("./say-hello.so") SayHello = libso.SayHello -SayHello.argtypes = [c_char_p] +SayHello.argtypes = [ctypes.c_char_p] SayHello.restype = None -SayHello(c_char_p(b"hello")) +SayHello(ctypes.c_char_p(b"hello")) diff --git a/examples/ch3-xx/add/add.go b/examples/ch3-xx/add/add.go new file mode 100644 index 0000000..307c12e --- /dev/null +++ b/examples/ch3-xx/add/add.go @@ -0,0 +1,17 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// Go版本, 支持内联优化 + +package add + +func Add(a, b int) int { + return a + b +} + +func AddSlice(dst, a, b []int) { + for i := 0; i < len(dst) && i < len(a) && i < len(b); i++ { + dst[i] = a[i] + b[i] + } + return +} diff --git a/examples/ch3-xx/add/add_asm.go b/examples/ch3-xx/add/add_asm.go new file mode 100644 index 0000000..054d6d6 --- /dev/null +++ b/examples/ch3-xx/add/add_asm.go @@ -0,0 +1,16 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build amd64 + +// 汇编版本, 不支持内联优化 + +package add + +func AsmAdd(a, b int) int + +func AsmAddSlice(dst, a, b []int) { + AddSlice(dst, a, b) +} + +func AsmAddSlice__todo(dst, a, b []int) diff --git a/examples/ch3-xx/add/add_asm_amd64.s b/examples/ch3-xx/add/add_asm_amd64.s new file mode 100644 index 0000000..d7492d8 --- /dev/null +++ b/examples/ch3-xx/add/add_asm_amd64.s @@ -0,0 +1,23 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +#include "textflag.h" + +// func AsmAdd(a, b int) int +TEXT ·AsmAdd(SB), NOSPLIT, $0-24 + MOVQ a+0(FP), AX // a + MOVQ b+8(FP), BX // b + ADDQ AX, BX // a+b + MOVQ BX, ret+16(FP) // return a+b + RET + +// func AsmAddSlice(dst, a, b []int) +TEXT ·AsmAddSlice__todo(SB), NOSPLIT, $0-72 + MOVQ dst+0(FP), AX // AX: dst + MOVQ a+24(FP), BX // BX: &a + MOVQ b+48(FP), CX // CX: &b + MOVQ dst_len+8(FP), DX // DX: len(dst) + MOVQ a_len+32(FP), R8 // R8: len(a) + MOVQ b_len+56(FP), R9 // R9: len(b) + // TODO: DX = min(DX,R8,R9) + RET diff --git a/examples/ch3-xx/add/add_asm_generic.go b/examples/ch3-xx/add/add_asm_generic.go new file mode 100644 index 0000000..b096716 --- /dev/null +++ b/examples/ch3-xx/add/add_asm_generic.go @@ -0,0 +1,16 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build !amd64 + +// 对于没有汇编实现的环境, 临时采用Go版本代替 + +package add + +func AsmAdd(a, b int) int { + return Add(a, b) +} + +func AsmAddSlice(dst, a, b []int) { + AddSlice(dst, a, b) +} diff --git a/examples/ch3-xx/add/add_test.go b/examples/ch3-xx/add/add_test.go new file mode 100644 index 0000000..b5cc477 --- /dev/null +++ b/examples/ch3-xx/add/add_test.go @@ -0,0 +1,97 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// go test -bench=. + +package add + +import ( + "testing" +) + +func TestAdd(t *testing.T) { + t.Run("go", func(t *testing.T) { + if x := Add(1, 2); x != 3 { + t.Fatalf("expect = %d, got = %d", 3, x) + } + }) + t.Run("asm", func(t *testing.T) { + if x := AsmAdd(1, 2); x != 3 { + t.Fatalf("expect = %d, got = %d", 3, x) + } + }) +} + +func TestAddSlice(t *testing.T) { + a := []int{1, 2, 3, 4, 5} + b := []int{10, 20, 30, 40, 50, 60} + + t.Run("go", func(t *testing.T) { + x := make([]int, len(a)) + AddSlice(x, a, b) + + for i := 0; i < len(x) && i < len(a) && i < len(b); i++ { + if x[i] != a[i]+b[i] { + t.Fatalf("expect = %d, got = %d", x[i], a[i]+b[i]) + } + } + }) + + t.Run("asm", func(t *testing.T) { + x := make([]int, len(a)) + AsmAddSlice(x, a, b) + + for i := 0; i < len(x) && i < len(a) && i < len(b); i++ { + if x[i] != a[i]+b[i] { + t.Fatalf("expect = %d, got = %d", x[i], a[i]+b[i]) + } + } + }) +} + +func BenchmarkAdd(b *testing.B) { + b.Run("go", func(b *testing.B) { + for i := 0; i < b.N; i++ { + Add(1, 2) + } + }) + b.Run("asm", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AsmAdd(1, 2) + } + }) +} + +func BenchmarkAddSlice(b *testing.B) { + s0 := make([]int, 10<<10) + s1 := make([]int, 10<<10) + dst := make([]int, 10<<10) + + b.Run("len=10", func(b *testing.B) { + dst := dst[:10] + for i := 0; i < b.N; i++ { + AddSlice(dst, s0, s1) + } + }) + b.Run("len=50", func(b *testing.B) { + dst := dst[:50] + for i := 0; i < b.N; i++ { + AddSlice(dst, s0, s1) + _ = dst + } + }) + b.Run("len=100", func(b *testing.B) { + dst := dst[:100] + for i := 0; i < b.N; i++ { + AddSlice(dst, s0, s1) + _ = dst + } + }) + b.Run("len=1000", func(b *testing.B) { + dst := dst[:1000] + for i := 0; i < b.N; i++ { + AddSlice(dst, s0, s1) + _ = dst + } + }) +} diff --git a/examples/ch3-xx/add/runme.go b/examples/ch3-xx/add/runme.go new file mode 100644 index 0000000..8a8f2c0 --- /dev/null +++ b/examples/ch3-xx/add/runme.go @@ -0,0 +1,14 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build ignore + +package main + +import ( + . "." +) + +func main() { + println("Add(1+2) =", Add(1, 2)) +} diff --git a/examples/ch3-xx/binary_search/binary_search.go b/examples/ch3-xx/binary_search/binary_search.go new file mode 100644 index 0000000..1d72aa4 --- /dev/null +++ b/examples/ch3-xx/binary_search/binary_search.go @@ -0,0 +1,6 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package bsearch + +func BinarySearch(arr []int, num int) bool diff --git a/examples/ch3-xx/binary_search/binary_search_amd64.s b/examples/ch3-xx/binary_search/binary_search_amd64.s new file mode 100644 index 0000000..de3827e --- /dev/null +++ b/examples/ch3-xx/binary_search/binary_search_amd64.s @@ -0,0 +1,45 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +TEXT ·BinarySearch+0(SB),$0 + +start: + MOVQ arr+0(FP), CX + MOVQ len+8(FP), AX + JMP find_index + +find_index: + XORQ DX, DX + MOVQ $2, BX + IDIVQ BX + JMP comp + +comp: + LEAQ (AX * 8), BX + ADDQ BX, CX + MOVQ num+24(FP), DX + CMPQ DX, (CX) + JE found + JG right + JL left + JMP not_found + +left: + CMPQ len+8(FP), $1 + JE not_found + MOVQ AX, len+8(FP) + JMP start + +right: + CMPQ len+8(FP), $1 + JE not_found + MOVQ CX, arr+0(FP) + JMP start + +not_found: + MOVQ $0, ret+32(FP) + RET + +found: + MOVQ $1, ret+32(FP) + RET diff --git a/examples/ch3-xx/binary_search/binary_search_test.go b/examples/ch3-xx/binary_search/binary_search_test.go new file mode 100644 index 0000000..504225c --- /dev/null +++ b/examples/ch3-xx/binary_search/binary_search_test.go @@ -0,0 +1,26 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package bsearch + +import "testing" + +func TestBinarySearch(t *testing.T) { + data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + + if result := BinarySearch(data, 8); result != true { + t.Errorf("Expected true value for binary search.") + } + + if result := BinarySearch(data, 1); result != true { + t.Errorf("Expected true value for binary search.") + } + + if result := BinarySearch(data, 10); result != true { + t.Errorf("Expected true value for binary search.") + } + + if result := BinarySearch(data, 12); result != false { + t.Errorf("Expected false value for binary search.") + } +} diff --git a/examples/ch3-xx/globalvar/asm_amd64.s b/examples/ch3-xx/globalvar/asm_amd64.s new file mode 100644 index 0000000..b32a02e --- /dev/null +++ b/examples/ch3-xx/globalvar/asm_amd64.s @@ -0,0 +1,26 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +#include "textflag.h" + +// func GetPkgValue() int +TEXT ·GetPkgValue(SB), NOSPLIT, $0-8 + MOVQ ·gopkgValue(SB), AX + MOVQ AX, ret+0(FP) + RET + +// func GetPkgInfo() PkgInfo +TEXT ·GetPkgInfo(SB), NOSPLIT, $0-24 + MOVBLZX ·gInfo+0(SB), AX // .V0 byte + MOVQ AX, ret+0(FP) + MOVWLZX ·gInfo+2(SB), AX // .V1 uint16 + MOVQ AX, ret+2(FP) + MOVLQZX ·gInfo+4(SB), AX // .V2 int32 + MOVQ AX, ret+4(FP) + MOVQ ·gInfo+8(SB), AX // .V3 int32 + MOVQ AX, ret+8(FP) + MOVBLZX ·gInfo+(16+0)(SB), AX // .V4 bool + MOVQ AX, ret+(16+0)(FP) + MOVBLZX ·gInfo+(16+1)(SB), AX // .V5 bool + MOVQ AX, ret+(16+1)(FP) + RET diff --git a/examples/ch3-xx/globalvar/globalvar.go b/examples/ch3-xx/globalvar/globalvar.go new file mode 100644 index 0000000..65696c6 --- /dev/null +++ b/examples/ch3-xx/globalvar/globalvar.go @@ -0,0 +1,32 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// 汇编中访问Go中定义的全局变量 + +package globalvar + +var gopkgValue int = 42 + +type PkgInfo struct { + V0 byte + V1 uint16 + V2 int32 + V3 int64 + V4 bool + V5 bool +} + +var gInfo PkgInfo + +func init() { + gInfo.V0 = 101 + gInfo.V1 = 102 + gInfo.V2 = 103 + gInfo.V3 = 104 + gInfo.V4 = true + gInfo.V5 = false +} + +func GetPkgValue() int + +func GetPkgInfo() PkgInfo diff --git a/examples/ch3-xx/globalvar/runme.go b/examples/ch3-xx/globalvar/runme.go new file mode 100644 index 0000000..db42bb5 --- /dev/null +++ b/examples/ch3-xx/globalvar/runme.go @@ -0,0 +1,17 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build ignore + +package main + +import ( + "fmt" + + . "." +) + +func main() { + fmt.Println(GetPkgValue()) + fmt.Println(GetPkgInfo()) +} diff --git a/examples/ch3-xx/hello/hello.go b/examples/ch3-xx/hello/hello.go new file mode 100644 index 0000000..ff96b94 --- /dev/null +++ b/examples/ch3-xx/hello/hello.go @@ -0,0 +1,10 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package hello + +var text = "你好, 世界, 包变量\n" + +func PrintHelloWorld() +func PrintHelloWorld_zh() +func PrintHelloWorld_var() diff --git a/examples/ch3-xx/hello/hello_amd64.s b/examples/ch3-xx/hello/hello_amd64.s new file mode 100644 index 0000000..8276bdb --- /dev/null +++ b/examples/ch3-xx/hello/hello_amd64.s @@ -0,0 +1,51 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +#include "textflag.h" +#include "funcdata.h" + +// "Hello World!\n" +DATA text<>+0(SB)/8,$"Hello Wo" +DATA text<>+8(SB)/8,$"rld!\n" +GLOBL text<>(SB),NOPTR,$16 + +// utf8: "你好, 世界!\n" +// hex: e4bda0e5a5bd2c20 e4b896e7958c210a +// len: 16 +DATA text_zh<>+0(SB)/8,$"\xe4\xbd\xa0\xe5\xa5\xbd\x2c\x20" +DATA text_zh<>+8(SB)/8,$"\xe4\xb8\x96\xe7\x95\x8c\x21\x0a" +GLOBL text_zh<>(SB),NOPTR,$16 + +// func PrintHelloWorld_var() +TEXT ·PrintHelloWorld_var(SB), $16-0 + NO_LOCAL_POINTERS + CALL runtime·printlock(SB) + MOVQ ·text+0(SB), AX + MOVQ AX, (SP) + MOVQ ·text+8(SB), AX + MOVQ AX, 8(SP) + CALL runtime·printstring(SB) + CALL runtime·printunlock(SB) + RET + +// func PrintHelloWorld() +TEXT ·PrintHelloWorld(SB), $16-0 + NO_LOCAL_POINTERS + CALL runtime·printlock(SB) + MOVQ $text<>+0(SB), AX + MOVQ AX, (SP) + MOVQ $16, 8(SP) + CALL runtime·printstring(SB) + CALL runtime·printunlock(SB) + RET + +// func PrintHelloWorld_zh() +TEXT ·PrintHelloWorld_zh(SB), $16-0 + NO_LOCAL_POINTERS + CALL runtime·printlock(SB) + MOVQ $text_zh<>+0(SB), AX + MOVQ AX, (SP) + MOVQ $16, 8(SP) + CALL runtime·printstring(SB) + CALL runtime·printunlock(SB) + RET diff --git a/examples/ch3-xx/hello/runme.go b/examples/ch3-xx/hello/runme.go new file mode 100644 index 0000000..4abd431 --- /dev/null +++ b/examples/ch3-xx/hello/runme.go @@ -0,0 +1,20 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build ignore + +package main + +import ( + "fmt" + + . "." +) + +func main() { + s := "你好, 世界!\n" + fmt.Printf("%d: %x\n", len(s), s) + PrintHelloWorld() + PrintHelloWorld_zh() + PrintHelloWorld_var() +} diff --git a/examples/ch3-xx/ifelse/ifelse.go b/examples/ch3-xx/ifelse/ifelse.go new file mode 100644 index 0000000..0a9a0e7 --- /dev/null +++ b/examples/ch3-xx/ifelse/ifelse.go @@ -0,0 +1,15 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// Go版本, 支持内联优化 + +package ifelse + +func If(ok bool, a, b int) int { + if ok { + return a + } + return b +} + +func AsmIf(ok bool, a, b int) int diff --git a/examples/ch3-xx/ifelse/ifelse_ams_amd64.s b/examples/ch3-xx/ifelse/ifelse_ams_amd64.s new file mode 100644 index 0000000..c10b73f --- /dev/null +++ b/examples/ch3-xx/ifelse/ifelse_ams_amd64.s @@ -0,0 +1,31 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +#include "textflag.h" + +// +// https://github.com/golang/go/issues/14288 +// +// from rsc: +// But expanding what I said yesterday just a bit: +// never use MOVB or MOVW with a register destination, +// since it's inefficient (it's a read-modify-write on the target register). +// Instead use MOVL for reg->reg and use MOVBLZX or MOVWLZX for mem->reg; +// those are pure writes on the target register. +// +// 因此, 加载bool型参数到寄存器时, 建议使用 MOVBLZX. +// 如果使用 MOVB 的话, go test 虽然通过了, +// 但是 go run runme.go 则出现错误结果. +// + +// func AsmIf(ok bool, a, b int) int +TEXT ·AsmIf(SB), NOSPLIT, $0-32 + MOVBQZX ok+0(FP), AX // ok + MOVQ a+8(FP), BX // a + MOVQ b+16(FP), CX // b + CMPQ AX, $0 // test ok + JEQ 3(PC) // if !ok, skip 2 line + MOVQ BX, ret+24(FP) // return a + RET + MOVQ CX, ret+24(FP) // return b + RET diff --git a/examples/ch3-xx/ifelse/ifelse_test.go b/examples/ch3-xx/ifelse/ifelse_test.go new file mode 100644 index 0000000..8309cba --- /dev/null +++ b/examples/ch3-xx/ifelse/ifelse_test.go @@ -0,0 +1,32 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// go test -bench=. + +package ifelse + +import ( + "testing" +) + +func TestMin(t *testing.T) { + t.Run("go", func(t *testing.T) { + if x := If(true, 1, 2); x != 1 { + t.Fatalf("expect = %d, got = %d", 1, x) + } + if x := If(false, 1, 2); x != 2 { + t.Fatalf("expect = %d, got = %d", 2, x) + } + }) + t.Run("asm", func(t *testing.T) { + if x := AsmIf(true, 1, 2); x != 1 { + t.Fatalf("expect = %d, got = %d", 1, x) + } + if x := AsmIf(false, 1, 2); x != 2 { + t.Fatalf("expect = %d, got = %d", 2, x) + } + if x := AsmIf(false, 2, 1); x != 1 { + t.Fatalf("expect = %d, got = %d", 1, x) + } + }) +} diff --git a/examples/ch3-xx/ifelse/runme.go b/examples/ch3-xx/ifelse/runme.go new file mode 100644 index 0000000..1920942 --- /dev/null +++ b/examples/ch3-xx/ifelse/runme.go @@ -0,0 +1,18 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build ignore + +package main + +import ( + . "." +) + +func main() { + println("If(true, 1, 2) =", If(true, 1, 2)) + println("If(false, 1, 2) =", If(false, 1, 2)) + println("AsmIf(true, 1, 2) =", AsmIf(true, 1, 2)) + println("AsmIf(false, 1, 2) =", AsmIf(false, 1, 2)) + println("AsmIf(false, 2, 1) =", AsmIf(false, 2, 1)) +} diff --git a/examples/ch3-xx/instr/bench_test.go b/examples/ch3-xx/instr/bench_test.go new file mode 100755 index 0000000..2ae33f5 --- /dev/null +++ b/examples/ch3-xx/instr/bench_test.go @@ -0,0 +1,22 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package instr + +import "testing" + +var g int64 + +func BenchmarkSum(b *testing.B) { + ns := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + for i := 0; i < b.N; i++ { + g = Sum(ns) + } +} + +func BenchmarkSum2(b *testing.B) { + ns := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + for i := 0; i < b.N; i++ { + g = Sum2(ns) + } +} diff --git a/examples/ch3-xx/instr/instr.go b/examples/ch3-xx/instr/instr.go new file mode 100755 index 0000000..2bbe396 --- /dev/null +++ b/examples/ch3-xx/instr/instr.go @@ -0,0 +1,26 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package instr + +func Add(n, m int64) int64 { + return n + m +} + +func Add2(n, m int64) int64 + +// BSF returns the index of the least significant set bit, +// or -1 if the input contains no set bits. +func BSF(n int64) int + +func BSF32(n int32) int32 + +func Sum(s []int64) int64 { + var ss int64 + for _, n := range s { + ss += n + } + return ss +} + +func Sum2(s []int64) int64 diff --git a/examples/ch3-xx/instr/instr_amd64.s b/examples/ch3-xx/instr/instr_amd64.s new file mode 100755 index 0000000..bfac1f3 --- /dev/null +++ b/examples/ch3-xx/instr/instr_amd64.s @@ -0,0 +1,57 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +#include "textflag.h" + +// func Add2(n, m int64) int32 +TEXT ·Add2(SB), NOSPLIT, $0-24 + MOVQ n+0(FP), AX + MOVQ m+8(FP), BX + ADDQ AX, BX + MOVQ BX, ret+16(FP) + RET + +// func BSF(n int64) int +TEXT ·BSF(SB), NOSPLIT, $0 + BSFQ n+0(FP), AX + JEQ allZero + MOVQ AX, ret+8(FP) + RET + +allZero: + MOVQ $-1, ret+8(FP) + RET + +// func BSF32(n int32) int32 +TEXT ·BSF32(SB), NOSPLIT, $0 + BSFL n+0(FP), AX + JEQ allZero32 + MOVL AX, ret+8(FP) + RET + +allZero32: + MOVL $-1, ret+8(FP) + RET + +// func Sum2(s []int64) int64 +TEXT ·Sum2(SB), NOSPLIT, $0 + MOVQ $0, DX + MOVQ s_base+0(FP), AX + MOVQ s_len+8(FP), DI + MOVQ $0, CX + CMPQ CX, DI + JGE Sum2End + +Sum2Loop: + MOVQ (AX), BP + ADDQ BP, DX + ADDQ $8, AX + INCQ CX + CMPQ CX, DI + JL Sum2Loop + +Sum2End: + MOVQ DX, ret+24(FP) + RET + +// vim: set ft=txt: diff --git a/examples/ch3-xx/loop/loop.go b/examples/ch3-xx/loop/loop.go new file mode 100644 index 0000000..b5174f5 --- /dev/null +++ b/examples/ch3-xx/loop/loop.go @@ -0,0 +1,16 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// Go版本, 支持内联优化 + +package loop + +func LoopAdd(cnt, v0, step int) int { + result := v0 + for i := 0; i < cnt; i++ { + result += step + } + return result +} + +func AsmLoopAdd(cnt, v0, step int) int diff --git a/examples/ch3-xx/loop/loop_asm_amd64.s b/examples/ch3-xx/loop/loop_asm_amd64.s new file mode 100644 index 0000000..38e7f03 --- /dev/null +++ b/examples/ch3-xx/loop/loop_asm_amd64.s @@ -0,0 +1,21 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +#include "textflag.h" + +// func AsmLoopAdd(cnt, v0, step int) int +TEXT ·AsmLoopAdd(SB), NOSPLIT, $0-32 + MOVQ cnt+0(FP), AX // cnt + MOVQ v0+8(FP), BX // v0 + MOVQ step+16(FP), CX // step + +loop: + CMPQ AX, $0 // compare cnt,0 + JLE end // if cnt <= 0: go end + DECQ AX // cnt-- + ADDQ CX, BX // v0 += step + JMP loop // goto loop + +end: + MOVQ BX, ret+24(FP) // return v0 + RET diff --git a/examples/ch3-xx/loop/loop_test.go b/examples/ch3-xx/loop/loop_test.go new file mode 100644 index 0000000..076042a --- /dev/null +++ b/examples/ch3-xx/loop/loop_test.go @@ -0,0 +1,54 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// go test -bench=. + +package loop + +import ( + "testing" +) + +func TestLoopAdd(t *testing.T) { + t.Run("go", func(t *testing.T) { + if x := LoopAdd(100, 0, 1); x != 100 { + t.Fatalf("expect = %d, got = %d", 100, x) + } + if x := LoopAdd(100, 0, 2); x != 200 { + t.Fatalf("expect = %d, got = %d", 200, x) + } + if x := LoopAdd(100, 0, -1); x != -100 { + t.Fatalf("expect = %d, got = %d", -100, x) + } + if x := LoopAdd(100, 50, 1); x != 150 { + t.Fatalf("expect = %d, got = %d", 150, x) + } + }) + t.Run("asm", func(t *testing.T) { + if x := AsmLoopAdd(100, 0, 1); x != 100 { + t.Fatalf("expect = %d, got = %d", 100, x) + } + if x := AsmLoopAdd(100, 0, 2); x != 200 { + t.Fatalf("expect = %d, got = %d", 200, x) + } + if x := AsmLoopAdd(100, 0, -1); x != -100 { + t.Fatalf("expect = %d, got = %d", -100, x) + } + if x := AsmLoopAdd(100, 50, 1); x != 150 { + t.Fatalf("expect = %d, got = %d", 150, x) + } + }) +} + +func BenchmarkLoopAdd(b *testing.B) { + b.Run("go", func(b *testing.B) { + for i := 0; i < b.N; i++ { + LoopAdd(1000, 0, 1) + } + }) + b.Run("asm", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AsmLoopAdd(1000, 0, 1) + } + }) +} diff --git a/examples/ch3-xx/loop/runme.go b/examples/ch3-xx/loop/runme.go new file mode 100644 index 0000000..3505c60 --- /dev/null +++ b/examples/ch3-xx/loop/runme.go @@ -0,0 +1,17 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build ignore + +package main + +import ( + . "." +) + +func main() { + println("LoopAdd(100,0,1) =", LoopAdd(100, 0, 1)) + println("LoopAdd(100,0,2) =", LoopAdd(100, 0, 2)) + println("LoopAdd(100,200,-1) =", LoopAdd(100, 200, -1)) + println("LoopAdd(100,0,-1) =", LoopAdd(100, 0, -1)) +} diff --git a/examples/ch3-xx/min/min.go b/examples/ch3-xx/min/min.go new file mode 100644 index 0000000..7faa628 --- /dev/null +++ b/examples/ch3-xx/min/min.go @@ -0,0 +1,31 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// Go版本, 支持内联优化 + +package min + +func Min(a, b int) int { + if a < b { + return a + } + return b +} + +//go:noinline +func MinNoInline(a, b int) int { + if a < b { + return a + } + return b +} + +func Max(a, b int) int { + if a > b { + return a + } + return b +} + +func AsmMin(a, b int) int +func AsmMax(a, b int) int diff --git a/examples/ch3-xx/min/min_asm_amd64.s b/examples/ch3-xx/min/min_asm_amd64.s new file mode 100644 index 0000000..60043a9 --- /dev/null +++ b/examples/ch3-xx/min/min_asm_amd64.s @@ -0,0 +1,26 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +#include "textflag.h" + +// func AsmMin(a, b int) int +TEXT ·AsmMin(SB), NOSPLIT, $0-24 + MOVQ a+0(FP), AX // a + MOVQ b+8(FP), BX // b + CMPQ AX, BX // compare a, b + JGT 3(PC) // if a>b, skip 2 line + MOVQ AX, ret+16(FP) // return a + RET + MOVQ BX, ret+16(FP) // return b + RET + +// func AsmMax(a, b int) int +TEXT ·AsmMax(SB), NOSPLIT, $0-24 + MOVQ a+0(FP), AX // a + MOVQ b+8(FP), BX // b + CMPQ AX, BX // compare a, b + JLT 3(PC) // if a. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// go test -bench=. + +package min + +import ( + "testing" +) + +func TestMin(t *testing.T) { + t.Run("go", func(t *testing.T) { + if x := Min(1, 2); x != 1 { + t.Fatalf("expect = %d, got = %d", 1, x) + } + if x := Min(2, 1); x != 1 { + t.Fatalf("expect = %d, got = %d", 1, x) + } + }) + t.Run("asm", func(t *testing.T) { + if x := AsmMin(1, 2); x != 1 { + t.Fatalf("expect = %d, got = %d", 1, x) + } + if x := AsmMin(2, 1); x != 1 { + t.Fatalf("expect = %d, got = %d", 1, x) + } + }) +} +func TestMax(t *testing.T) { + t.Run("go", func(t *testing.T) { + if x := Max(1, 2); x != 2 { + t.Fatalf("expect = %d, got = %d", 2, x) + } + if x := Max(2, 1); x != 2 { + t.Fatalf("expect = %d, got = %d", 2, x) + } + }) + t.Run("asm", func(t *testing.T) { + if x := AsmMax(1, 2); x != 2 { + t.Fatalf("expect = %d, got = %d", 2, x) + } + if x := AsmMax(2, 1); x != 2 { + t.Fatalf("expect = %d, got = %d", 2, x) + } + }) +} + +func BenchmarkMin(b *testing.B) { + b.Run("go", func(b *testing.B) { + for i := 0; i < b.N; i++ { + Min(1, 2) + } + }) + b.Run("go.noinline", func(b *testing.B) { + for i := 0; i < b.N; i++ { + MinNoInline(1, 2) + } + }) + b.Run("asm", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AsmMin(1, 2) + } + }) +} diff --git a/examples/ch3-xx/min/runme.go b/examples/ch3-xx/min/runme.go new file mode 100644 index 0000000..66fc42e --- /dev/null +++ b/examples/ch3-xx/min/runme.go @@ -0,0 +1,14 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build ignore + +package main + +import ( + . "." +) + +func main() { + println("Min(1,2) =", Min(1, 2)) +} diff --git a/examples/ch3-xx/slice/runme.go b/examples/ch3-xx/slice/runme.go new file mode 100644 index 0000000..8336e92 --- /dev/null +++ b/examples/ch3-xx/slice/runme.go @@ -0,0 +1,15 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// +build ignore + +package main + +import ( + . "." +) + +func main() { + println("SumIntSlice([]int{1,2,3}) =", SumIntSlice([]int{1, 2, 3})) + println("AsmSumIntSlice([]int{1,2,3}) =", AsmSumIntSlice([]int{1, 2, 3})) +} diff --git a/examples/ch3-xx/slice/slice.go b/examples/ch3-xx/slice/slice.go new file mode 100644 index 0000000..f3cf545 --- /dev/null +++ b/examples/ch3-xx/slice/slice.go @@ -0,0 +1,35 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// Go版本, 支持内联优化 + +package slice + +func SumIntSlice(s []int) int { + var sum int + for _, v := range s { + sum += v + } + return sum +} + +func SumFloat32Slice(s []float32) float32 { + var sum float32 + for _, v := range s { + sum += v + } + return sum +} + +func SumFloat64Slice(s []float64) float64 { + var sum float64 + for _, v := range s { + sum += v + } + return sum +} + +func AsmSumInt16Slice(v []int16) int16 + +func AsmSumIntSlice(s []int) int +func AsmSumIntSliceV2(s []int) int diff --git a/examples/ch3-xx/slice/slice_asm_amd64.s b/examples/ch3-xx/slice/slice_asm_amd64.s new file mode 100644 index 0000000..e1eb290 --- /dev/null +++ b/examples/ch3-xx/slice/slice_asm_amd64.s @@ -0,0 +1,59 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +#include "textflag.h" + +// func AsmSumInt16Slice(v []int16) int16 +TEXT ·AsmSumInt16Slice(SB), NOSPLIT, $0-26 + MOVQ v_base+0(FP), R8 + MOVQ v_len+8(FP), R9 + SHLQ $1, R9 + ADDQ R8, R9 + MOVQ $0, R10 + +loop: + CMPQ R8, R9 + JE end + ADDW (R8), R10 + ADDQ $2, R8 + JMP loop + +end: + MOVW R10, ret+24(FP) + RET + +// func AsmSumIntSlice(s []int) int +TEXT ·AsmSumIntSlice(SB), NOSPLIT, $0-32 + MOVQ s+0(FP), AX // &s[0] + MOVQ s_len+8(FP), BX // len(s) + MOVQ $0, CX // sum = 0 + +loop: + CMPQ BX, $0 // compare cnt,0 + JLE end // if cnt <= 0: goto end + DECQ BX // cnt-- + ADDQ (AX), CX // sum += s[i] + ADDQ $8, AX // i++ + JMP loop // goto loop + +end: + MOVQ CX, ret+24(FP) // return sum + RET + +// func AsmSumIntSliceV2(s []int) int +TEXT ·AsmSumIntSliceV2(SB), NOSPLIT, $0-32 + MOVQ s+0(FP), AX // p := &s[0] + MOVQ s_len+8(FP), BX + LEAQ 0(AX)(BX*8), BX // p_end := &s[len(s)] + MOVQ $0, CX // sum = 0 + +loop: + CMPQ AX, BX // compare p,p_end + JGE end // if p >= p_end: goto end + ADDQ (AX), CX // sum += s[i] + ADDQ $8, AX // p++ + JMP loop // goto loop + +end: + MOVQ CX, ret+24(FP) // return sum + RET diff --git a/examples/ch3-xx/slice/slice_test.go b/examples/ch3-xx/slice/slice_test.go new file mode 100644 index 0000000..eaeca23 --- /dev/null +++ b/examples/ch3-xx/slice/slice_test.go @@ -0,0 +1,82 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// go test -bench=. + +package slice + +import ( + "testing" +) + +func TestLoopAdd(t *testing.T) { + t.Run("go", func(t *testing.T) { + if x := SumIntSlice([]int{1, 2, 3}); x != 6 { + t.Fatalf("expect = %d, got = %d", 6, x) + } + }) + t.Run("asm", func(t *testing.T) { + if x := AsmSumIntSlice([]int{1, 2, 3}); x != 6 { + t.Fatalf("expect = %d, got = %d", 6, x) + } + }) + t.Run("asm.v2", func(t *testing.T) { + if x := AsmSumIntSliceV2([]int{1, 2, 3}); x != 6 { + t.Fatalf("expect = %d, got = %d", 6, x) + } + }) +} + +func BenchmarkLoopAdd(b *testing.B) { + s10 := make([]int, 10) + s100 := make([]int, 100) + s1000 := make([]int, 1000) + + b.Run("go/len=10", func(b *testing.B) { + for i := 0; i < b.N; i++ { + SumIntSlice(s10) + } + }) + b.Run("asm/len=10", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AsmSumIntSlice(s10) + } + }) + b.Run("asm.v2/len=10", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AsmSumIntSliceV2(s10) + } + }) + + b.Run("go/len=100", func(b *testing.B) { + for i := 0; i < b.N; i++ { + SumIntSlice(s100) + } + }) + b.Run("asm/len=100", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AsmSumIntSlice(s100) + } + }) + b.Run("asm.v2/len=100", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AsmSumIntSliceV2(s100) + } + }) + + b.Run("go/len=1000", func(b *testing.B) { + for i := 0; i < b.N; i++ { + SumIntSlice(s1000) + } + }) + b.Run("asm/len=1000", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AsmSumIntSlice(s1000) + } + }) + b.Run("asm.v2/len=1000", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AsmSumIntSliceV2(s1000) + } + }) +} diff --git a/examples/ch3-xx/stackmap/stackmap.go b/examples/ch3-xx/stackmap/stackmap.go new file mode 100755 index 0000000..82964c8 --- /dev/null +++ b/examples/ch3-xx/stackmap/stackmap.go @@ -0,0 +1,25 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package stackmap + +func X(b []byte) []byte + +//func X(b []byte) []byte { +// if len(b) == cap(b) { +// b = growSlice(b) +// } +// b = b[:len(b)+1] +// b[len(b)-1] = 3 +// return b +//} + +func growSlice(b []byte) []byte { + newCap := 10 + if cap(b) > 5 { + newCap = cap(b) * 2 + } + b1 := make([]byte, len(b), newCap) + copy(b1, b) + return b1 +} diff --git a/examples/ch3-xx/stackmap/stackmap_amd64.s b/examples/ch3-xx/stackmap/stackmap_amd64.s new file mode 100755 index 0000000..55cb134 --- /dev/null +++ b/examples/ch3-xx/stackmap/stackmap_amd64.s @@ -0,0 +1,44 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +#include "funcdata.h" +#include "textflag.h" + +// func X(b []byte) []byte +TEXT ·X(SB), $48-48 + MOVQ b_base+0(FP), BX + MOVQ b_len+8(FP), CX + MOVQ b_cap+16(FP), DX + + CMPQ CX, DX + JL afterGrow + + // Set up the growSlice call. + MOVQ BX, gs_base-48(SP) + MOVQ CX, gs_len-40(SP) + MOVQ DX, gs_cap-32(SP) + + CALL ·growSlice(SB) + + MOVQ gs_base-24(SP), BX + MOVQ gs_len-16(SP), CX + MOVQ gs_cap-8(SP), DX + +afterGrow: + // At this point, we have adequate capacity to increase len + 1 and the + // following register scheme: + // BX - b_base + // CX - b_len + // DX - b_cap + + // Write base/cap results. + MOVQ BX, ret_base+24(FP) + MOVQ DX, ret_cap+40(FP) + + // Write new element to b and increment the length. + LEAQ (BX)(CX*1), BX + MOVB $3, (BX) + ADDQ $1, CX + MOVQ CX, ret_len+32(FP) + + RET diff --git a/examples/ch3-xx/stackmap/stackmap_test.go b/examples/ch3-xx/stackmap/stackmap_test.go new file mode 100755 index 0000000..4116132 --- /dev/null +++ b/examples/ch3-xx/stackmap/stackmap_test.go @@ -0,0 +1,47 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package stackmap + +import ( + "bytes" + "testing" +) + +func TestX(t *testing.T) { + b := make([]byte, 0, 3) + + for _, want := range [][]byte{ + mkSlice(3, 3), + mkSlice(3, 3, 3), + mkSlice(3, 3, 3, 3), + mkSlice(10, 3, 3, 3, 3), + mkSlice(10, 3, 3, 3, 3, 3), + mkSlice(10, 3, 3, 3, 3, 3, 3), + mkSlice(10, 3, 3, 3, 3, 3, 3, 3), + mkSlice(10, 3, 3, 3, 3, 3, 3, 3, 3), + mkSlice(10, 3, 3, 3, 3, 3, 3, 3, 3, 3), + mkSlice(10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3), + mkSlice(20, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3), + } { + b = X(b) + if !slicesEqual(b, want) { + t.Fatalf("got %v[cap=%d]; want %v[cap=%d]", + b, cap(b), want, cap(want)) + } + } +} + +func mkSlice(cap int, vs ...byte) []byte { + b1 := make([]byte, 0, cap) + for _, v := range vs { + b1 = append(b1, v) + } + return b1 +} +func slicesEqual(b0, b1 []byte) bool { + if cap(b0) != cap(b1) { + return false + } + return bytes.Equal(b0, b1) +} diff --git a/examples/ch3-xx/sum/sum.go b/examples/ch3-xx/sum/sum.go new file mode 100644 index 0000000..ddc536c --- /dev/null +++ b/examples/ch3-xx/sum/sum.go @@ -0,0 +1,6 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package sum + +func Sum(a int, b int) int diff --git a/examples/ch3-xx/sum/sum_amd64.s b/examples/ch3-xx/sum/sum_amd64.s new file mode 100644 index 0000000..e88c85e --- /dev/null +++ b/examples/ch3-xx/sum/sum_amd64.s @@ -0,0 +1,9 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +TEXT ·Sum+0(SB),$0 + MOVQ a+0(FP), BX + MOVQ b+8(FP), BP + ADDQ BP, BX + MOVQ BX, return+16(FP) + RET diff --git a/examples/ch3-xx/sum/sum_test.go b/examples/ch3-xx/sum/sum_test.go new file mode 100644 index 0000000..f8a4551 --- /dev/null +++ b/examples/ch3-xx/sum/sum_test.go @@ -0,0 +1,14 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package sum + +import "testing" + +func TestSum(t *testing.T) { + result := Sum(1, 1) + + if result != 2 { + t.Errorf("%d does not equal 2", result) + } +} diff --git a/examples/ch3-xx/vector/sum_amd64.s b/examples/ch3-xx/vector/sum_amd64.s new file mode 100644 index 0000000..caa74db --- /dev/null +++ b/examples/ch3-xx/vector/sum_amd64.s @@ -0,0 +1,11 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +TEXT ·SumVec+0(SB), $0 + MOVQ vec1+0(FP), BX // Move the first vector into BX + MOVQ vec2+24(FP), CX // Move the second vector into BX + MOVUPS (BX), X0 + MOVUPS (CX), X1 + ADDPS X0, X1 + MOVUPS X1, result+48(FP) + RET diff --git a/examples/ch3-xx/vector/vector.go b/examples/ch3-xx/vector/vector.go new file mode 100644 index 0000000..705d591 --- /dev/null +++ b/examples/ch3-xx/vector/vector.go @@ -0,0 +1,8 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package vector + +func Find(vec []int, num int) bool + +func SumVec(vec1 []int32, vec2 []int32) [4]int32 diff --git a/examples/ch3-xx/vector/vector_amd64.s b/examples/ch3-xx/vector/vector_amd64.s new file mode 100644 index 0000000..2698edc --- /dev/null +++ b/examples/ch3-xx/vector/vector_amd64.s @@ -0,0 +1,28 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +TEXT ·Find+0(SB),$0 + MOVQ $0, SI // zero the iterator + MOVQ vec+0(FP), BX // BX = &vec[0] + MOVQ vec+8(FP), CX // len(vec) + MOVQ num+24(FP), DX + +start: + CMPQ SI, CX + JG notfound + CMPQ (BX), DX + JNE notequal + JE found + +found: + MOVQ $1, return+32(FP) + RET + +notequal: + INCQ SI + LEAQ +8(BX), BX + JMP start + +notfound: + MOVQ $0, return+32(FP) + RET diff --git a/examples/ch3-xx/vector/vector_test.go b/examples/ch3-xx/vector/vector_test.go new file mode 100644 index 0000000..97b6949 --- /dev/null +++ b/examples/ch3-xx/vector/vector_test.go @@ -0,0 +1,40 @@ +// Copyright © 2017 ChaiShushan . +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package vector + +import "testing" + +func TestFind(t *testing.T) { + vec := []int{1, 2, 3, 4, 5, 6, 7, 8} + if result := Find(vec, 5); result != true { + t.Errorf("Could not find number in vector, got: %v", result) + } + + if result := Find(vec, 10); result != false { + t.Errorf("Returned true when false was expected") + } +} + +func TestSum(t *testing.T) { + vec1 := []int32{1, 2, 3, 5} + vec2 := []int32{1, 2, 3, 5} + + result := SumVec(vec1, vec2) + + if result[0] != 2 { + t.Errorf("Expected 2, got %v, result was: %v", result[0], result) + } + + if result[1] != 4 { + t.Errorf("Expected 4, got %v, result was: %v", result[0], result) + } + + if result[2] != 6 { + t.Errorf("Expected 6, got %v, result was: %v", result[0], result) + } + + if result[3] != 10 { + t.Errorf("Expected 10, got %v, result was: %v", result[0], result) + } +} diff --git a/images/Makefile b/images/Makefile new file mode 100644 index 0000000..6a63adc --- /dev/null +++ b/images/Makefile @@ -0,0 +1,31 @@ +# Copyright 2017 . All rights reserved. +# Use of this source code is governed by a Apache +# license that can be found in the LICENSE file. + +UML_FILES=$(wildcard ./*.plantuml) +UML_SVG_FILES=$(patsubst %.plantuml,%.uml.svg,$(UML_FILES)) +UML_PNG_FILES=$(patsubst %.plantuml,%.uml.png,$(UML_FILES)) + +DOT_FILES=$(wildcard ./*.dot) +DOT_PNG_FILES=$(patsubst %.dot,%.dot.png,$(DOT_FILES)) + +default: $(UML_PNG_FILES) $(DOT_PNG_FILES) + @echo "ok" + +logo: + convert logo-2018-01.png -resize 300x65 logo.png + @echo "ok" + +clean: + -rm *.dot.png + -rm *.uml.png + -rm *.uml.svg + +%.uml.svg: %.plantuml + cat $< | docker run --rm -i chai2010/ibox:plantuml > $@ + +%.uml.png: %.plantuml + cat $< | docker run --rm -i chai2010/ibox:plantuml -tpng > $@ + +%.dot.png: %.dot + dot -Tpng -o $@ $< diff --git a/images/ch1-01-go-family-tree-x.dot.png b/images/ch1-01-go-family-tree-x.dot.png new file mode 100644 index 0000000..feb8adb Binary files /dev/null and b/images/ch1-01-go-family-tree-x.dot.png differ diff --git a/images/ch2-call-c-sum-v1.plantuml b/images/ch2-call-c-sum-v1.plantuml new file mode 100644 index 0000000..59a92d9 --- /dev/null +++ b/images/ch2-call-c-sum-v1.plantuml @@ -0,0 +1,46 @@ +' Copyright 2017 . All rights reserved. +' Use of this source code is governed by a Apache +' license that can be found in the LICENSE file. + +@startuml + +title C.sum(2, 3) + +|main.go| +:func main() { + C.sum(2, 3)) +}; + +|#AntiqueWhite|main.cgo1.go| +:func main() { + _Cfunc_sum(2, 3)) +}; + +|#AntiqueWhite|_cgo_types.go| +:func _Cfunc_sum(2, 3) double { + _cgo_runtime_cgocall(...) + return +}; + +|runtime.cgocall| +:_cgo_runtime_cgocall(...); + +fork + +|#AntiqueWhite|main.cgo2.c| +: double _cgo_xxx_Cfunc_sum(2, 3) { + return sum(2, 3) +}; + +|#AntiqueWhite|_cgo_export.c| +:sum(2, 3); + +endfork + +|runtime.cgocall| +:_cgo_runtime_cgocall(...); + +|main.go| +stop + +@enduml diff --git a/images/ch2-call-c-sum-v1.uml.png b/images/ch2-call-c-sum-v1.uml.png new file mode 100644 index 0000000..c4930ef Binary files /dev/null and b/images/ch2-call-c-sum-v1.uml.png differ diff --git a/images/ch2-call-c-sum-v2.plantuml b/images/ch2-call-c-sum-v2.plantuml new file mode 100644 index 0000000..9e1286b --- /dev/null +++ b/images/ch2-call-c-sum-v2.plantuml @@ -0,0 +1,63 @@ +' Copyright 2017 . All rights reserved. +' Use of this source code is governed by a Apache +' license that can be found in the LICENSE file. + +@startuml + +title c call go func + +|_testmain.c| +:int main() { + extern int sum(int a, int b) + sum(1, 2) + return 0 +}; +-[#green,dashed]-> + +fork + +|#AntiqueWhite|_cgo_export.c| +:int sum(int p0, int p1) { + struct { int p0, p1, r0; } a + _cgo_ctxt = _cgo_wait_runtime_init_done() + crosscall2(_cgoexp_xxx_sum, &a, 16, _cgo_ctxt) + _cgo_release_context(_cgo_ctxt) + return a.r0 +}; + +|runtime/cgo/*.asm| +:TEXT crosscall2(SB),NOSPLIT,$0; + +fork + +|#AntiqueWhite|_cgo_types.go| +:func _cgoexp_xxx_sum(a unsafe.Pointer, n int32, ctxt uintptr) { + fn := _cgoexpwrap_xxx_sum + _cgo_runtime_cgocallback( + _cgoexpwrap_xxx_sum, + ... + ) +}; + +|#AntiqueWhite|_cgo_types.go| +:func _cgoexpwrap_xxx_sum(p0, p1) r0 { + return sum(p0, p1) +}; + +|main.go| +://export sum +func sum(a, b C.int) C.int { + return a + b +}; + +endfork + +|runtime/cgo/*.asm| +:TEXT crosscall2(SB),NOSPLIT,$0; + +endfork + +|_testmain.c| +stop + +@enduml diff --git a/images/ch2-call-c-sum-v2.uml.png b/images/ch2-call-c-sum-v2.uml.png new file mode 100644 index 0000000..6c8bbc2 Binary files /dev/null and b/images/ch2-call-c-sum-v2.uml.png differ diff --git a/images/ch2-cgo-generated-files.dot b/images/ch2-cgo-generated-files.dot new file mode 100644 index 0000000..8b0da04 --- /dev/null +++ b/images/ch2-cgo-generated-files.dot @@ -0,0 +1,37 @@ +// Copyright 2018 . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +digraph G { + subgraph cluster_cgo_package_main { + label = "package main"; + + nocgo_x[label="nocgo_x.go", style=filled, color=gray]; + hello_go[label="hello.go", style=filled, color=orangered4]; + main_go[label="main.go", style=filled, color=darkgreen]; + nocgo_1[label="nocgo_1.go", style=filled, color=gray]; + } + + subgraph cluster_cgo_generated_files { + label = "cgo: generated fileds"; + + main_cgo2_c[label="main.cgo2.c", style=filled, color=green]; + main_cgo1_go[label="main.cgo1.go", style=filled, color=green]; + + hello_cgo2_c[label="hello.cgo2.c", style=filled, color=red]; + hello_cgo1_go[label="hello.cgo1.go", style=filled, color=red]; + + _cgo_gotypes_go[label="_cgo_gotypes.go", shape=box, style=filled, color=dodgerblue]; + _cgo_export_h[label="_cgo_export.h", shape=box, style=filled, color=dodgerblue]; + _cgo_export_c[label="_cgo_export.c", shape=box, style=filled, color=dodgerblue]; + + main_go -> main_cgo1_go -> _cgo_gotypes_go; + main_go -> main_cgo2_c -> _cgo_gotypes_go; + + hello_go -> hello_cgo1_go -> _cgo_gotypes_go; + hello_go -> hello_cgo2_c -> _cgo_gotypes_go; + + _cgo_gotypes_go -> _cgo_export_h; + _cgo_gotypes_go -> _cgo_export_c; + } +} diff --git a/images/ch2-cgo-generated-files.dot.png b/images/ch2-cgo-generated-files.dot.png new file mode 100644 index 0000000..dab55af Binary files /dev/null and b/images/ch2-cgo-generated-files.dot.png differ diff --git a/images/ch2-int32-to-char-ptr.plantuml b/images/ch2-int32-to-char-ptr.plantuml new file mode 100644 index 0000000..72d16e1 --- /dev/null +++ b/images/ch2-int32-to-char-ptr.plantuml @@ -0,0 +1,29 @@ +' Copyright 2017 . All rights reserved. +' Use of this source code is governed by a Apache +' license that can be found in the LICENSE file. + +@startuml + +title int32 <=> *C.char + +participant int32 +participant uintptr +participant unsafe.Pointer as unsafe_Pointer +participant "~*C.char" as c_char_ptr + +== Go == + +int32 -> uintptr: int32 to uintptr +uintptr -> unsafe_Pointer: uintptr to unsafe.Pointer + +== CGO == + +unsafe_Pointer -> c_char_ptr: unsafe.Pointer to *C.char +c_char_ptr -> unsafe_Pointer: ~*C.char to unsafe.Pointer + +== Go == + +unsafe_Pointer -> uintptr: unsafe.Pointer to uintptr +uintptr -> int32: uintptr to int32 + +@enduml diff --git a/images/ch2-int32-to-char-ptr.uml.png b/images/ch2-int32-to-char-ptr.uml.png new file mode 100644 index 0000000..4aa6190 Binary files /dev/null and b/images/ch2-int32-to-char-ptr.uml.png differ diff --git a/images/ch2-qsort-v2.plantuml b/images/ch2-qsort-v2.plantuml new file mode 100644 index 0000000..7516eae --- /dev/null +++ b/images/ch2-qsort-v2.plantuml @@ -0,0 +1,88 @@ +' Copyright 2017 . All rights reserved. +' Use of this source code is governed by a Apache +' license that can be found in the LICENSE file. + +@startuml + +title qsort + +participant go +participant c + +[--> go: qsort +activate go + +go -> go +activate go #DarkSalmon +note left + go_qsort_compare_info.elemsize = sv.Type().Elem().Size() + go_qsort_compare_info.fn = fn +end note + +deactivate go + +go -> c: C.qsort_proxy(cmp=go_qsort_compare) +activate c +note right +void qsort_proxy( + void* base, size_t num, size_t size, + int (*compare)(const void* a, const void* b) +) { + go_qsort_compare_save_base(base); + qsort(base, num, size, compare); +} +end note + +' begin + c -> c: C.go_qsort_compare_save_base + activate c #DarkSalmon + note right: callback go func + + go <- c: go_qsort_compare_save_base + activate go #DarkSalmon + note left + //export go_qsort_compare_save_base + func go_qsort_compare_save_base(base unsafe.Pointer) { + go_qsort_compare_info.base = uintptr(base) + } + var go_qsort_compare_info struct { + base uintptr + elemsize uintptr + fn func(a, b int) int + sync.RWMutex + } + end note + + go -> c: go_qsort_compare_save_base done + deactivate go + + deactivate c +' end + +loop + c -> c: C.go_qsort_compare + activate c #DarkSalmon + note right: callback go func + + c -> go: go_qsort_compare + activate go #DarkSalmon + note left + //export go_qsort_compare_save_base + func go_qsort_compare_save_base(base unsafe.Pointer) { + go_qsort_compare_info.base = uintptr(base) + } + end note + + go -> c: go_qsort_compare done + deactivate go + + deactivate c +end + +go <- c: C.qsort_proxy done +deactivate c + +[<-- go: done +deactivate go + +@enduml diff --git a/images/ch2-qsort-v2.uml.png b/images/ch2-qsort-v2.uml.png new file mode 100644 index 0000000..a197397 Binary files /dev/null and b/images/ch2-qsort-v2.uml.png differ diff --git a/images/ch2-x-ptr-to-y-ptr.plantuml b/images/ch2-x-ptr-to-y-ptr.plantuml new file mode 100644 index 0000000..7333271 --- /dev/null +++ b/images/ch2-x-ptr-to-y-ptr.plantuml @@ -0,0 +1,23 @@ +' Copyright 2017 . All rights reserved. +' Use of this source code is governed by a Apache +' license that can be found in the LICENSE file. + +@startuml + +title *X <=> *Y + +participant "~*X" as x_ptr +participant unsafe.Pointer as unsafe_Pointer +participant "~*Y" as y_ptr + +== *X => *Y == + +x_ptr -> unsafe_Pointer: ~*X to unsafe.Pointer +unsafe_Pointer -> y_ptr: unsafe.Pointer to *Y + +== *Y => *X == + +y_ptr -> unsafe_Pointer: ~*Y to unsafe.Pointer +unsafe_Pointer -> x_ptr: unsafe.Pointer to *X + +@enduml diff --git a/images/ch2-x-ptr-to-y-ptr.uml.png b/images/ch2-x-ptr-to-y-ptr.uml.png new file mode 100644 index 0000000..574ece5 Binary files /dev/null and b/images/ch2-x-ptr-to-y-ptr.uml.png differ diff --git a/images/ch2-x-slice-to-y-slice.plantuml b/images/ch2-x-slice-to-y-slice.plantuml new file mode 100644 index 0000000..66cee0a --- /dev/null +++ b/images/ch2-x-slice-to-y-slice.plantuml @@ -0,0 +1,36 @@ +' Copyright 2017 . All rights reserved. +' Use of this source code is governed by a Apache +' license that can be found in the LICENSE file. + + +'var p []X +'var q []Y // q = p + +'pHdr := (*reflect.SliceHeader)(unsafe.Pointer(&p)) +'qHdr := (*reflect.SliceHeader)(unsafe.Pointer(&q)) + +'pHdr.Data = qHdr.Data +'pHdr.Len = qHdr.Len * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0]) +'pHdr.Cap = qHdr.Cap * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0]) + + +@startuml + +title []X <=> []Y + +participant "var x []X\nvar y []Y" as slice +participant "var px *SliceHeader\nvar py *SliceHeader" as slice_header + +== []X and []Y to *reflect.SliceHeader == + +slice -> slice_header: px := (*reflect.SliceHeader)(unsafe.Pointer(&x))\npy := (*reflect.SliceHeader)(unsafe.Pointer(&y)) + +== copy *px to *py == + +slice_header -> slice_header: ~*py = *px + +== y changed by *py == + +slice -> slice: y = x + +@enduml diff --git a/images/ch2-x-slice-to-y-slice.uml.png b/images/ch2-x-slice-to-y-slice.uml.png new file mode 100644 index 0000000..9b6ad2b Binary files /dev/null and b/images/ch2-x-slice-to-y-slice.uml.png differ diff --git a/server.go b/server.go index 252dcf3..0bf15bf 100644 --- a/server.go +++ b/server.go @@ -19,6 +19,7 @@ import ( var ( flagRootDir = flag.String("dir", ".", "root dir") flagHttpAddr = flag.String("http", ":8080", "HTTP service address") + flagNoCache = flag.Bool("no-cache", true, "disable browser cache") flagOpenBrowser = flag.Bool("openbrowser", true, "open browser automatically") ) @@ -47,7 +48,11 @@ func main() { log.Printf("Hit CTRL-C to stop the server\n") }() - log.Fatal(http.ListenAndServe(httpAddr, http.FileServer(http.Dir(*flagRootDir)))) + mainHandler := http.FileServer(http.Dir(*flagRootDir)) + if *flagNoCache { + mainHandler = NoCache(mainHandler) + } + log.Fatal(http.ListenAndServe(httpAddr, mainHandler)) } // waitServer waits some time for the http Server to start @@ -97,3 +102,41 @@ func startBrowser(url string) bool { cmd := exec.Command(args[0], append(args[1:], url)...) return cmd.Start() == nil } + +var epoch = time.Unix(0, 0).Format(time.RFC1123) + +var noCacheHeaders = map[string]string{ + "Expires": epoch, + "Cache-Control": "no-cache, private, max-age=0", + "Pragma": "no-cache", + "X-Accel-Expires": "0", +} + +var etagHeaders = []string{ + "ETag", + "If-Modified-Since", + "If-Match", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", +} + +func NoCache(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + // Delete any ETag headers that may have been set + for _, v := range etagHeaders { + if r.Header.Get(v) != "" { + r.Header.Del(v) + } + } + + // Set our NoCache headers + for k, v := range noCacheHeaders { + w.Header().Set(k, v) + } + + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} diff --git a/talks/.gitignore b/talks/.gitignore new file mode 100644 index 0000000..9c1c72d --- /dev/null +++ b/talks/.gitignore @@ -0,0 +1 @@ +!chai2010-cgo-talk-sz-20110207.pdf diff --git a/talks/README.md b/talks/README.md new file mode 100644 index 0000000..8c663fe --- /dev/null +++ b/talks/README.md @@ -0,0 +1,4 @@ +# 相关 报告 + +- [Go集成C/C++代码(PDF)](chai2010-cgo-talk-sz-20110207.pdf) - chai2010, 珠三角技术沙龙深圳(2011.02.27) +- [深入CGO编程](https://mp.weixin.qq.com/s/1_v8DsvinZWfh0-HXks88Q) - by chai2010, GopherChina 2018 中国上海(2018.04.14~15) (内容将在大会前一天公开, 先占个坑) diff --git a/talks/chai2010-cgo-talk-sz-20110207.pdf b/talks/chai2010-cgo-talk-sz-20110207.pdf new file mode 100644 index 0000000..97ea6fc Binary files /dev/null and b/talks/chai2010-cgo-talk-sz-20110207.pdf differ diff --git a/talks/gopherchina2018-cgo-talk/README.md b/talks/gopherchina2018-cgo-talk/README.md new file mode 100644 index 0000000..d4373c4 --- /dev/null +++ b/talks/gopherchina2018-cgo-talk/README.md @@ -0,0 +1,3 @@ +# GopherChina 2018 报告 + +- [深入CGO编程](https://mp.weixin.qq.com/s/1_v8DsvinZWfh0-HXks88Q) - by chai2010 (内容将在大会前一天公开, 先占个坑)