• 2010-04-30

    Hello,LLVM

    LLVM是Low Level Virtual Machine的缩写,虽然名字叫了虚拟机,但它的目标可很宏伟,要做一套编译器的基础架构,从编译到运行无所不包。

    构建LLVM

    先来下载LLVM的发行版本
        http://llvm.org/releases/

    每个LLVM的发行版里都包括很多内容,比如为LLVM提供的C语言前端Clang,通向JVM和.NET的VMKit,给gcc提供的插件DragonEgg等等。这里,我们选择从LLVM源码开始。

    LLVM以CMake作为构建系统,以便在不同的平台上用不同的编译器都可以进行构建。为了更好的对构建内容进行统一,我们在LLVM的目录下建立一个build目录,这样,就可以把所有生成的内容都放到这个目录下。进入到这个目录下
        llvm/build$ cmake ..
    这个命令运行的结果会在build目录下生成一个Makefile。上面只是按照缺省配置进行生成,我们也可以对其定制,比如:
        llvm/build$ cmake .. -DCMAKE_INSTALL_PREFIX=/home/dreamhead/applilcations/llvm -DBUILD_SHARED_LIBS=ON
    通过CMAKE_INSTALL_PREFIX,我们指定了安装的路径,让BUILD_SHARED_LIBS等于ON,生成的就是动态库,而非静态库。

    有了Makefile,就可以进行真正的构建了。
        llvm/build$ make

    经过漫长的等待,最终会生成LLVM的库,然后就可以进行安装了
        llvm/build$ make install

    例子时间

    在下面这个例子里面,我们用LLVM的指令集创建了一个非常简单的函数,求和,它等价于:
      int add(int a, int b) {
        return a + b;
      }
    之后,我们以1和2作为参数调用了这个函数,并把运行结果显示出来。

    #include "llvm/LLVMContext.h"
    #include "llvm/Module.h"
    #include "llvm/Function.h"
    #include "llvm/Analysis/Verifier.h"
    #include "llvm/ExecutionEngine/GenericValue.h"
    #include "llvm/ExecutionEngine/JIT.h"
    #include "llvm/DerivedTypes.h"
    #include "llvm/Support/IRBuilder.h"
    #include "llvm/Support/raw_ostream.h"
    #include "llvm/Target/TargetSelect.h"

    using namespace llvm;
    using namespace std;

    Function* create_add_function(Module* module, LLVMContext& context) {
      // 函数基本信息
      Constant* c = module->getOrInsertFunction("add", // 函数名
        // 返回值
        IntegerType::get(context, 32),                                            
        // 函数参数
        IntegerType::get(context, 32), IntegerType::get(context, 32),
        NULL);
      Function* add = cast<Function> (c);
      // 调用约定
      add->setCallingConv(CallingConv::C);                           

      // 参数名
      Function::arg_iterator args = add->arg_begin();
      Value* a = args++;
      a->setName("a");
      Value* b = args++;
      b->setName("b");

      // 指令块
      BasicBlock* block = BasicBlock::Create(context, "entry", add);
      IRBuilder<> builder(block);
      // tmp = a + b
      Value* tmp = builder.CreateBinOp(Instruction::Add, a, b, "tmp");
      // return tmp
      builder.CreateRet(tmp);

      return add;
    }

    int main(int argc, char** argv) {
      // 创建Context
      LLVMContext& context = getGlobalContext();
      // 创建Module
      Module* module = new Module("test", context);
      // 基于Module和Context创建函数
      Function* add = create_add_function(module, context);

      // 校验Module
      verifyModule(*module, PrintMessageAction);

      // 输出Module的内容
      outs() << *module << "\n";

      // 准备运行函数
      InitializeNativeTarget();
      ExecutionEngine* engine = EngineBuilder(module).create();

      // 准备实参
      vector<GenericValue> args(2);
      args[0].IntVal = APInt(32, 1);
      args[1].IntVal = APInt(32, 2);

      // 调用函数
      GenericValue result = engine->runFunction(add, args);
      // 显示返回值
      outs() << "Result: " << result.IntVal << "\n";

      delete module;

      return 0;
    }

    接下来就是编译了,因为LLVM编译链接牵扯到很多细节,所以,它提供了一个llvm-config为我们生成这些编译选项。
      g++ -o llvm-add main.cpp `llvm-config --cxxflags --ldflags --libs jit nativecodegen`

    好,一切就绪,运行!
      ./llvm-add

    输出如下:
    ; ModuleID = 'test'

    define i32 @add(i32 %a, i32 %b) {
    entry:
      %tmp = add i32 %a, %b                           ; <i32> [#uses=1]
      ret i32 %tmp
    }

    Result: 3

  • 2010-04-01

    Hello,Redis

    NoSQL正试图打破关系数据型数据库一统天下的局面。Redis也是NoSQL的一种尝试。Redis是什么?其网站上如是说:
      A persistent key-value database with built-in net interface written in ANSI-C for Posix systems

    Redis最值得拿出来说事的优点就是“快”!Github就用了Redis为自己加速

    我们选择下载稳定的发布版:
      http://code.google.com/p/redis/downloads/list
    也可以直接获取源码:
      http://github.com/antirez/redis

    现在的redis与一般的C开源项目相比,非常简单,只有很少的一些文件。它的构建过程也不像其它的项目那些,需要先配置再构建。它只有一个简单的makefile,构建它,只要make一下即可。当然,正如其声称的那样,目前redis只是为Posix系统而打造,所以,如果想在Windows上构建,也许Cygwin是个选择。

    先来做一下简单的体验,启动redis的服务器端:
      > ./redis-server

    redis本身还附带了一个命令行的客户端,用它就可以对redis本身进行测试:
      > ./redis-cli set key value
      OK
      > ./redis-cli get key
      value

    在上面的这个会话里面,我们通过设置的一个键值对(key和value),然后,用键值取回其对应的值,非常简单。除了最简单的键值对,Redis还支持list、set和有序的set。下面的会话是对list的操作:
      > ./redis-cli lpush list 1
      OK
      > ./redis-cli lpush list 2
      OK
      > ./redis-cli lrange list 0 -1
      1. 2
      2. 1
      > ./redis-cli rpush list 3
      OK
      > ./redis-cli lrange list 0 -1
      1. 2
      2. 1
      3. 3
    这里的lpush命令在list的头部添加元素,rpush命令在list的尾部添加元素,而lrange用来查询list里面的元素。

    上面我们用的命令行对Redis进行操作,实际上,Redis本身就是具备网络接口的数据库,它有着自己的协议,一种简单的文本协议。这个协议本身并不复杂,只要实现了这个协议就可以与Redis进行互联互通了,所以,Redis有着不同语言的客户端实现,而上面用到的命令行也是用同样的协议实现的。

    通过以最普通的telnet直连Redis,我们稍微了解一下这个协议。
      > telnet localhost 6379
    这里的6379是Redis的缺省端口,下面是一段简单的会话:
      SET key 5
      value
      +OK
      GET key
      $5
      value
      QUIT
    这段会话等同上面命令行的第一段会话。SET是命令字,key是键值,5是随后值的长度,value就是值的内容,这就是我们发送的内容,+OK是服务器端给出的应答,告诉我们操作成功。然后,我们获取key对应的值,GET是命令字,key是键值,之后是服务器给出的应答,$5是值的长度,value是值的内容。最后的QUIT是断开连接的命令字。

    如此简单的Redis,不妨把它当做了解NoSQL的起步台阶。

  • 给《Programming Scala》的作者发邮件澄清一个问题时,我突然意识到之前写的一些关于伴生对象的说法是基于Scala 2.8的讨论。对于现在的稳定版本2.7还是有一些差异的,于是补遗一篇。

    还是上篇的那段代码:
    object Companion {
      def show = println("I am a companion")
    }

    class Companion {
      def shout = Companion.show
    }
    (Companion.scala)

    这次,我们用Scala 2.7编译,然后,反编译:

    public class Companion extends java.lang.Object implements scala.ScalaObject{
        public Companion();
        public void show();
        public int $tag()       throws java.rmi.RemoteException;
    }

    我们忽略$tag()。对比于2.8编译出的版本,show()这个static方法不见了。换句话说,使用2.7编译出来的版本,如果我们想访问show()的话,只能这么写:
      Companion$.MODULE$.show();

    放心,虽然作为Java程序员,我们不太习惯这种写法,但这确实是一段可以编译运行的Java代码。

    Singleton是没有这样的问题,之前所说的内容还是适用的,我们还是可以用static方法的。从这个角度而言,在Scala 2.7里,Singleton和伴生对象的处理是不统一的,而到了2.8,它们就走到了一起。

    不过,在伴生对象的处理上,Scala 2.8还有一个小细节需要注意,如果class和object里有同名的方法,则无法生成static方法。也就是说,如果我们把代码改成这样:
    object Companion {
      def show = println("I am a companion")
    }

    class Companion {
      def show = Companion.show
    }

    再用Scala 2.8反编译,结果就成了:

    public class Companion extends java.lang.Object implements scala.ScalaObject{
        public Companion();
        public void show();
    }

    这也很容易理解,在Java里面,实例方法和static方法也是不能同名的,不信你试试。

    Companion$.MODULE$.show(),虽然这样的写法很诡异,但对于Java操作Scala代码来说,这确实屡试不爽的,无论是字节码是用哪个版本Scala编译出来的。但从软件设计的角度而言,Scala 2.8的实现做了更好的统一,让整个模型更容易理解,是一种更值得推荐的做法。

  • 准备涉水Scala的Java程序员请注意,Scala里没有static。

    在大多数情况下,static真不该是static的。像Scala这样想在面向对象上更进一步的程序设计语言,取消static是一种进取的表现,这样得以保证了其面向对象模型的完整性。好吧,我承认,有时候,我们还是需要类一级的属性和操作的。在Scala里,我们还是有机会的,这便是伴生对象(Companion Object)的作用。

    下面就是一个伴生对象的例子:

    object Companion {
      def show = println("I am a companion")
    }

    class Companion {
      def shout = Companion.show
    }
    (Companion.scala)

    这个object就是我们所说的伴生对象,如果读过《走进Scala——Singleton》,你会觉得这个伴生对象和Singleton异曲同工,实际上,是这样的。伴生对象本身就是一个Singleton,不同的是,它有一个与之同名的类(这里的class Companion),二者可以相互访问彼此的私有成员。在这里,我们暂且不关心私有成员的相互访问。

    编译一下:
      scalac Companion.scala

    同Singleton一样,我们也得到了两个文件:Companion.class和Companion$.class。我们还可以用javap查看反编译的结果,其中,Companion$.class与之前的Singleton$.class几近相同,这里就省略了。一起来看看Companion.class。
      javap Companion

    public class Companion extends java.lang.Object implements scala.ScalaObject{
        public static final void show();
        public Companion();
        public void shout();
    }

    因为有了对应的class,object成了伴生对象。从结果可以看出,伴生对象和它对应的类在字节码层面走到了一起(Companion类)。换句话说,在Scala里面的class和object在Java层面里面合二为一,class里面的成员成了实例成员,object成员成了static成员。我们已经知道,这里的static成员只是一个简单的wrapper,封装了实际的操作。

    对应到反编译的代码上,我们看到了与object相关的那个static方法——show。因为要构建Companion的实例,所以,生成的代码里有构造函数。此外,class Companion的实例方法shout在字节码层面上也体现到了Companion类里。

    至此,我们已经对伴生对象有了一个基本的了解。在Scala的层面上,我们把分属于类和实例分开放置,从代码的组织而言,会更加清晰。在实现层面上,它们都是按照对象处理的(分别用Companion$和Companion),从而达到了对象模型的统一。

    最后,我想说,请善用object。

  • Singlton是一种为许多人熟知的设计模式,到了Scala这里,它成了语言的一部分,换句换说,我们不必像Java那样费劲的自己实现。下面就是一个Singleton:

    object Singleton {
      def show = println("I am a singleton")
    }
    (Singleton.scala)

    这里,我们用object进行声明,它会创建这个一个类,这个类只有一个实例,名字就叫Singleton。我们可以这样使用它:
      Singleton.show

    编译一下:
      scalac Singleton.scala

    不同于类的编译,Singleton编译出两个.class文件:Singleton.class和Singleton$.class。其实,真正称得上是Singleton的类是Singleton$,反编译一下,就可以看出来:
      javap -c Singleton$

    输出如下:
    public final class Singleton$ extends java.lang.Object implements scala.ScalaObject{
    public static final Singleton$ MODULE$;

    public static {};
      Code:
       0:    new    #10; //class Singleton$
       3:    invokespecial    #13; //Method "<init>":()V
       6:    return

    public void show();
      ...
    }

    其中,MODULE$是这个类唯一的实例,这个实例是在static块创建出来的。所以,Singleton.show这样的语句到了JVM的层面,就会变成这样:
      Singleton$.MODULE$.show();

    不知道你是否注意到,这个类里根本没有构造函数,换句话说,站在使用者的角度,我们根本就没有机会为这个类创建对象。如果你还不太适应这个没有构造函数的世界,可以参考《Javac背后的故事——空类》。

    我们都知道,javac会为没有构造函数的类生成一个缺省的构造函数,所以,在Java代码里,如果我们想实现Singleton必须显式声明出一个private的构造函数。而scalac绕过了javac直接生成字节码,它给出了一个用Java语言无法实现的Singleton方案。

    如果说Singleton$是真正的Singleton实现,那么还有个Singleton类是干什么的呢?先来反编译,看看它做了些什么:
      javap -c Singleton

    输出如下:
    public final class Singleton extends java.lang.Object{
    public static final void show();
      Code:
       0:    getstatic    #11; //Field Singleton$.MODULE$:LSingleton$;
       3:    invokevirtual    #13; //Method Singleton$.show:()V
       6:    return
    }

    我们看到了,这里有一个方法里同样有一个show方法,不同与Singleton$里的实现,它是static的,而它几乎是完全的透传,也就是说,这个方法实现是:
      public static final void show() {
        Singleton$.MODULE$.show();
      }

    这样一来,与Java互操作时,我们只要写Singleton.show()就可以了,不必理会Singleton$.MODULE$.show()这种对Java程序员来说很诡异的写法。不过,这还不是全部。