1. 24
  1.  

  2. 8

    Brooks Davis has given a nice talk about what needs to be working to get a C hello world running. Note that his version assumes that the compiler is transforming the printf to puts. The printf function itself is a stress tests for a C compiler (clang could compile all of GNUstep except for GSFormat, which is basically printf for about a year before it could compile all of GNUstep). The locale support and variadic argument handling make this tricky in C and if you use the GNU extensions that allow you to register other format specifiers, it’s even more painful. In C++, the iostream goo is pretty horrific at the source level but doesn’t compile down to anything much worse than the C standard library. Java’s printf ends up invoking the class loader to load locale classes (Sun’s libc did something similar for locales), which ends up invoking pretty much every part of the JVM (file I/O, class loader, security policies, threads, JIT, GC), so is a surprisingly good test - if you can do printf, your JVM probably works.

    1. 1

      This looks really great and very much in the same spirit as the post, I will check it out soon, thanks!

    2. 6

      Just for kicks, here’s “hello world” in my Mu project:

      fn main -> exit-status/ebx: int {
        print-string 0, "Hello world!\n"
        exit-status <- copy 0
      }
      

      (link; colorized)

      Run it like this: (x86 only. Linux only for now.)

      $ git clone https://github.com/akkartik/mu
      $ cd mu
      $ ./translate_mu apps/hello.mu
      $ ./a.elf
      Hello world!
      $
      

      The work is done by print-string in layer 405, which takes a screen object and a string to print. If the screen object is null, it prints to the real screen. Fake screens are useful for tests, though they aren’t fully implemented yet.

      If the screen is null/real, print-string delegates to print-string-to-real-string, which is implemented in machine code in layer 304:

      print-string-to-real-screen:  # s: (addr array byte)
          # . prologue
          55/push-ebp
          89/<- %ebp 4/r32/esp
          #
          (write 1 *(ebp+8))
      $print-string-to-real-screen:end:
          # . epilogue
          89/<- %esp 5/r32/ebp
          5d/pop-to-ebp
          c3/return
      

      (link; colorized)

      The hex at the start of each word goes straight into the binary. The rest is for documentation or error-checking.

      The function call (write 1 *(ebp+8)) is syntax sugar for pushing args, performing a call, and popping args:

      ff 6/subop/push *(ebp+8)
      68/push 1/imm32
      e8/call write/disp32
      81 0/subop/add %esp 8/imm32
      

      write is implemented at layer 108 to support tests, and delegates to _write at layer 101 which passes length-prefixed arrays to the Linux write() syscall.

      1. 2

        This is a great article. Thanks!

        1. 2

          thank you for reading it :)