Hello World in QuakeC Bytecode

Keywords: quake
Created: 2020-05-22 Fri 18:46
Table of Contents

Picking apart the Bytecode of a hello world program compiled with QuakeC .

Input Program

// print is the builtin function #1
void   (string str)          print     = #1;

void() main = {
  print("hello world");
};

I'm using the print builtin of qcvm and the original qcc for compiling.

Header

00000000: 0600 0000 5054 0000 7400 0000 0400 0000  ....PT..t.......
00000010: 0001 0000 0400 0000 2001 0000 0100 0000  ........ .......
00000020: 9400 0000 0300 0000 3c00 0000 3800 0000  ........<...8...
00000030: 2801 0000 1f00 0000 0000 0000            (...........
  • 4 bytes, version: 0x06
  • 2 bytes, CRC: 0x5054

Note : Values in big-endian.

Block Offset Size
Statements 0x0074 0x0004
Defs 0x0100 0x0004
Fields 0x0120 0x0001
Functions 0x0094 0x0003
Strings 0x003c 0x0038
Globals 0x0128 0x001f

Strings

56 bytes of NULL-terminated strings.

00000030:                               0074 6573              .tes
00000040: 742e 7163 0070 7269 6e74 0068 656c 6c6f  t.qc.print.hello
00000050: 2077 6f72 6c64 006d 6169 6e00 7072 696e   world.main.prin
00000060: 7400 6d61 696e 0049 4d4d 4544 4941 5445  t.main.IMMEDIATE
00000070: 0000 0000                                ....

We see that the string table generated by the compiler is not optimal as print and main are included twice, once for use in the function section and once for use in the definitions section.

Statements

4 statements, 8 bytes each.

00000070:           0000 0000 0000 0000 2000 1e00      ........ ...
00000080: 0400 0000 3400 1c00 0000 0000 0000 0000  ....4...........
00000090: 0000 0000                                ....
OpCode Arg1 Arg2 Arg3
DONE
STORE_V 0x1e 0x04
CALL_1 0x1c
DONE

In the globals section, we'll see that global 0x1e has value 0x0f and global 0x1c has value 0x01 .

0x04 is the offset of the first parameter. 0x0f is the offset of "hello world" in the string section.

Again, the code generated by qcc is not optimal, the argument is not a vector so STORE_F would be sufficient (assuming STORE_F is faster than STORE_V ).

See QuakeC Bytecode Opcodes for a list all opcodes.

Functions

3 functions, 36 bytes each.

00000090:           0000 0000 0000 0000 0000 0000      ............
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000b0: 0000 0000 0000 0000 ffff ffff 1d00 0000  ................
000000c0: 0000 0000 0000 0000 0900 0000 0100 0000  ................
000000d0: 0100 0000 0100 0000 0000 0000 0100 0000  ................
000000e0: 1e00 0000 0000 0000 0000 0000 1b00 0000  ................
000000f0: 0100 0000 0000 0000 0000 0000 0000 0000  ................
Entry 1st Local Locals Name File n-args argsize
0 0x00 0 EMPTY EMPTY 0
-1 0x1d 0 print test.qc 1 1
1 0x1e 0 main test.qc 0

Definitions

4 definitions, 8 bytes each.

00000100: 0000 0000 0000 0000 0600 1c00 2000 0000  ............ ...
00000110: 0600 1d00 2600 0000 0100 1e00 2b00 0000  ....&.......+...
Type Global Offset Name
void no 0x00 EMPTY
function no 0x1c print
function no 0x1d main
string no 0x1e IMMEDIATE

Fields

1 field(s), 8 bytes each.

00000120: 0000 0000 0000 0000                      ........
Type Offset Name
void 0x00 EMPTY

Globals

31 globals, 4 bytes each.

00000120:                     0000 0000 0000 0000          ........
00000130: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000140: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000150: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000160: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000170: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000180: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000190: 0000 0000 0000 0000 0100 0000 0200 0000  ................
000001a0: 0f00 0000                                ....
Global Value
0x00 0x1b 0x00
0x1c 0x01
0x1d 0x02
0x1e 0x0f

The globals 0x00 to 0x1b are reserved:

  • 0x00 , NULL
  • 0x01 , return value
  • 0x04 , parameter 1
  • 0x07 , parameter 2
  • 0x0a , parameter 3
  • 0x0d , parameter 4
  • 0x10 , parameter 5
  • 0x13 , parameter 6
  • 0x16 , parameter 7
  • 0x19 , parameter 8

Return and parameter globals are spaced in increments of 3 to allow returning / passing vectors as arguments. For example, when passing a vector as the first argument, the x component would be stored in 0x04 , y in 0x05 and z in 0x06 .

Interpreting the Program

The program is run by jumping to the entry point of the main function at instruction 0x01 .

STORE_V 0x1e 0x04 stores global 0x1e , a string containing "hello world" , in global 0x04 , the first parameter of a function call. (Because qcc generated the vector variant of STORE , it also stores 0x1f in 0x05 and 0x20 in 0x06 ).

CALL_1 0x1c calls the function defined by global 0x1c , in this case the builtin 1, (function entry -1) which takes one argument, the string to be printed.

The last instruction is DONE , ending the execution of the program.


Last export: 2020-07-12 Sun 22:29
This document is also available as plain text and pdf.

If you have an idea how this page could be improved or a comment send me a mail.