TC-X Samples

The goal of TC-X is straightforward: starting from LIR, generate the IA-32 instructions, except that you don’t have actual registers: we still heavily use Temp s. Register allocation has been (or will be) done in another stage, TC-9, Register Allocation.

the-answer-ia32.tig
let
  var answer := 42
in
  answer := 51
end
tc --target-ia32 --inst-display the-answer-ia32.tig
$ tc --target-ia32 --inst-display the-answer-ia32.tig
/** Tiger final assembler ouput. */

/** Routine: _main */
	.text
	.globl	tc_main
	.type	tc_main,@function
tc_main:
# Allocate frame
	movl	%ebx, %t3
	movl	%edi, %t4
	movl	%esi, %t5
l0:
	movl	$42, %t1
	movl	%t1, (%ebp)
	movl	$51, %t2
	movl	%t2, (%ebp)
l1:
	movl	%t3, %ebx
	movl	%t4, %edi
	movl	%t5, %esi
# Deallocate frame
	ret	$0
l2:
	.size	tc_main,l2-tc_main
	.ident	"LRDE Tiger Compiler"
$ echo $?
0

At this stage the compiler cannot know what registers are used; the frame is not allocated. The final stage, register allocation, addresses this issue. For your information, it results in:

tc --target-ia32 -sI the-answer-ia32.tig
$ tc --target-ia32 -sI the-answer-ia32.tig
/** Tiger final assembler ouput. */

/** Routine: _main */
	.text
	.globl	tc_main
	.type	tc_main,@function
tc_main:
	pushl	%ebp
	subl	$4, %esp
	movl	%esp, %ebp
	subl	$4, %esp
l0:
	movl	$42, %ecx
	movl	%ecx, (%ebp)
	movl	$51, %ecx
	movl	%ecx, (%ebp)
l1:
	addl	$4, %ebp
	leave
	ret	$0
l2:
	.size	tc_main,l2-tc_main
	.ident	"LRDE Tiger Compiler"
$ echo $?
0

A delicate part of this exercise is handling the function calls:

add-ia32.tig
let
  function add(x: int, y: int) : int = x + y
in
  print_int(add(1,(add(2, 3)))); print("\n")
end
tc -e --target-ia32 --inst-display add-ia32.tig
$ tc -e --target-ia32 --inst-display add-ia32.tig
/** Tiger final assembler ouput. */

/** Routine: add */
	.text
	.globl	tc_l0
	.type	tc_l0,@function
tc_l0:
# Allocate frame
	movl	12(%ebp), %t11
	movl	%t11, (%ebp)
	movl	16(%ebp), %t0
	movl	20(%ebp), %t1
	movl	%ebx, %t8
	movl	%edi, %t9
	movl	%esi, %t10
l2:
	movl	%t0, %t7
	addl	%t1, %t7
	movl	%t7, %eax
l3:
	movl	%t8, %ebx
	movl	%t9, %edi
	movl	%t10, %esi
# Deallocate frame
	ret	$12
l6:
	.size	tc_l0,l6-tc_l0

	.section	.rodata
l1:
	.long 1
	.asciz "\n"

/** Routine: _main */
	.text
	.globl	tc_main
	.type	tc_main,@function
tc_main:
# Allocate frame
	movl	%ebx, %t16
	movl	%edi, %t17
	movl	%esi, %t18
l4:
	movl	%ebp, %t5
	movl	$3, %t12
	pushl	%t12
	movl	$2, %t13
	pushl	%t13
	pushl	%ebp
	call	tc_l0
	movl	%eax, %t4
	pushl	%t4
	movl	$1, %t14
	pushl	%t14
	pushl	%t5
	call	tc_l0
	movl	%eax, %t6
	pushl	%t6
	call	tc_print_int
	lea	l1, %t15
	pushl	%t15
	call	tc_print
l5:
	movl	%t16, %ebx
	movl	%t17, %edi
	movl	%t18, %esi
# Deallocate frame
	ret	$0
l7:
	.size	tc_main,l7-tc_main
	.ident	"LRDE Tiger Compiler"
$ echo $?
0

You must be able to handle functions with any number of arguments:

many-args-ia32.tig
let
  function many(a0 : int, a1 : int, a2 : int, a3 : int, a4 : int,
                a5 : int, a6 : int, a7 : int, a8 : int, a9 : int): int =
    a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9
in
  print_int(many(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
  print("\n")
end
tc -e --target-ia32 --inst-display many-args-ia32.tig
$ tc -e --target-ia32 --inst-display many-args-ia32.tig
/** Tiger final assembler ouput. */

/** Routine: many */
	.text
	.globl	tc_l0
	.type	tc_l0,@function
tc_l0:
# Allocate frame
	movl	12(%ebp), %t25
	movl	%t25, (%ebp)
	movl	16(%ebp), %t0
	movl	20(%ebp), %t1
	movl	24(%ebp), %t2
	movl	28(%ebp), %t3
	movl	32(%ebp), %t4
	movl	36(%ebp), %t5
	movl	40(%ebp), %t6
	movl	44(%ebp), %t7
	movl	48(%ebp), %t8
	movl	52(%ebp), %t9
	movl	%ebx, %t22
	movl	%edi, %t23
	movl	%esi, %t24
l2:
	movl	%t0, %t13
	addl	%t1, %t13
	movl	%t13, %t14
	addl	%t2, %t14
	movl	%t14, %t15
	addl	%t3, %t15
	movl	%t15, %t16
	addl	%t4, %t16
	movl	%t16, %t17
	addl	%t5, %t17
	movl	%t17, %t18
	addl	%t6, %t18
	movl	%t18, %t19
	addl	%t7, %t19
	movl	%t19, %t20
	addl	%t8, %t20
	movl	%t20, %t21
	addl	%t9, %t21
	movl	%t21, %eax
l3:
	movl	%t22, %ebx
	movl	%t23, %edi
	movl	%t24, %esi
# Deallocate frame
	ret	$44
l6:
	.size	tc_l0,l6-tc_l0

	.section	.rodata
l1:
	.long 1
	.asciz "\n"

/** Routine: _main */
	.text
	.globl	tc_main
	.type	tc_main,@function
tc_main:
# Allocate frame
	movl	%ebx, %t37
	movl	%edi, %t38
	movl	%esi, %t39
l4:
	movl	$9, %t26
	pushl	%t26
	movl	$8, %t27
	pushl	%t27
	movl	$7, %t28
	pushl	%t28
	movl	$6, %t29
	pushl	%t29
	movl	$5, %t30
	pushl	%t30
	movl	$4, %t31
	pushl	%t31
	movl	$3, %t32
	pushl	%t32
	movl	$2, %t33
	pushl	%t33
	movl	$1, %t34
	pushl	%t34
	movl	$0, %t35
	pushl	%t35
	pushl	%ebp
	call	tc_l0
	movl	%eax, %t12
	pushl	%t12
	call	tc_print_int
	lea	l1, %t36
	pushl	%t36
	call	tc_print
l5:
	movl	%t37, %ebx
	movl	%t38, %edi
	movl	%t39, %esi
# Deallocate frame
	ret	$0
l7:
	.size	tc_main,l7-tc_main
	.ident	"LRDE Tiger Compiler"
$ echo $?
0

The runtime must be functional. No difference must be observable in comparison with a run with HAVM.

substring-0-1-1-ia32.tig
substring("", 1, 1)
tc -e --target-ia32 --inst-display substring-0-1-1-ia32.tig
$ tc -e --target-ia32 --inst-display substring-0-1-1-ia32.tig
/** Tiger final assembler ouput. */

	.section	.rodata
l0:
	.long 0
	.asciz ""

/** Routine: _main */
	.text
	.globl	tc_main
	.type	tc_main,@function
tc_main:
# Allocate frame
	movl	%ebx, %t4
	movl	%edi, %t5
	movl	%esi, %t6
l1:
	movl	$1, %t1
	pushl	%t1
	movl	$1, %t2
	pushl	%t2
	lea	l0, %t3
	pushl	%t3
	call	tc_substring
l2:
	movl	%t4, %ebx
	movl	%t5, %edi
	movl	%t6, %esi
# Deallocate frame
	ret	$0
l3:
	.size	tc_main,l3-tc_main
	.ident	"LRDE Tiger Compiler"
$ echo $?
0
tc -e --target-ia32 --asm-compute --inst-display substring-0-1-1-ia32.tig
$ tc -e --target-ia32 --asm-compute --inst-display substring-0-1-1-ia32.tig
/** Tiger final assembler ouput. */

	.section	.rodata
l0:
	.long 0
	.asciz ""

/** Routine: _main */
	.text
	.globl	tc_main
	.type	tc_main,@function
tc_main:
	pushl	%ebp
	subl	$4, %esp
	movl	%esp, %ebp
	subl	$0, %esp
l1:
	movl	$1, %ecx
	pushl	%ecx
	movl	$1, %ecx
	pushl	%ecx
	lea	l0, %ecx
	pushl	%ecx
	call	tc_substring
l2:
	addl	$4, %ebp
	leave
	ret	$0
l3:
	.size	tc_main,l3-tc_main
	.ident	"LRDE Tiger Compiler"
$ echo $?
0
tc -e --target-ia32 --asm-display substring-0-1-1-ia32.tig > substring-0-1-1-ia32.s
$ tc -e --target-ia32 --asm-display substring-0-1-1-ia32.tig > substring-0-1-1-ia32.s

$ echo $?
0
gcc -m32 -osubstring-0-1-1-ia32 substring-0-1-1-ia32.s
$ gcc -m32 -osubstring-0-1-1-ia32 substring-0-1-1-ia32.s
/usr/bin/ld: /tmp/ccj5QnUo.o: warning: relocation in read-only section `.text'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
$ echo $?
0
./substring-0-1-1-ia32
$ ./substring-0-1-1-ia32
substring: arguments out of bounds
$ echo $?
120

The following example illustrates conditional jumps.

condjump-ia32.tig
if 42 > 51 then "forty-two" else "fifty-one"
tc -e --target-ia32 --inst-display condjump-ia32.tig
$ tc -e --target-ia32 --inst-display condjump-ia32.tig
/** Tiger final assembler ouput. */

	.section	.rodata
l0:
	.long 9
	.asciz "forty-two"

	.section	.rodata
l1:
	.long 9
	.asciz "fifty-one"

/** Routine: _main */
	.text
	.globl	tc_main
	.type	tc_main,@function
tc_main:
# Allocate frame
	movl	%ebx, %t4
	movl	%edi, %t5
	movl	%esi, %t6
l5:
	movl	$42, %t1
	cmp	$51, %t1
	jg	l2
l3:
	lea	l1, %t2
l4:
	jmp	l6
l2:
	lea	l0, %t3
	jmp	l4
l6:
	movl	%t4, %ebx
	movl	%t5, %edi
	movl	%t6, %esi
# Deallocate frame
	ret	$0
l7:
	.size	tc_main,l7-tc_main
	.ident	"LRDE Tiger Compiler"
$ echo $?
0
tc -e --target-ia32 --asm-compute --inst-display condjump-ia32.tig
$ tc -e --target-ia32 --asm-compute --inst-display condjump-ia32.tig
/** Tiger final assembler ouput. */

	.section	.rodata
l0:
	.long 9
	.asciz "forty-two"

	.section	.rodata
l1:
	.long 9
	.asciz "fifty-one"

/** Routine: _main */
	.text
	.globl	tc_main
	.type	tc_main,@function
tc_main:
	pushl	%ebp
	subl	$4, %esp
	movl	%esp, %ebp
	subl	$0, %esp
l5:
	movl	$42, %ecx
	cmp	$51, %ecx
	jg	l2
l3:
	lea	l1, %ecx
l4:
	jmp	l6
l2:
	lea	l0, %ecx
	jmp	l4
l6:
	addl	$4, %ebp
	leave
	ret	$0
l7:
	.size	tc_main,l7-tc_main
	.ident	"LRDE Tiger Compiler"
$ echo $?
0