Skip to content

Instantly share code, notes, and snippets.

@mrnugget
Created September 8, 2019 06:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrnugget/1b4ccece43ac1a1aff3a161ac169d7bb to your computer and use it in GitHub Desktop.
Save mrnugget/1b4ccece43ac1a1aff3a161ac169d7bb to your computer and use it in GitHub Desktop.

Scheme x86 Compiler - Debugging wrong call

Input:

((lambda (g x) (g x)) (lambda (x) (prim-apply + 1 x)) 2)

Problem: generated code contains two functions.

We want to jump to (lambda (g x) (g x)) (label_0) first.

Instead, the code jumps to the (lambda (x) (prim-apply + 1 x)) (label_1) first.

Debugging with GDB

(gdb) p $label_1
$2 = {<text variable, no debug info>} 0x565e6a30 <label_1>
(gdb) p $label_0
$3 = {<text variable, no debug info>} 0x565e6a42 <label_0>

We want to jump to label_0 first.

Dissecting the evaluation of arguments. First we evaluate the closure at label_1:

  movl 16(%esp), %esi
  movl $label_1, %eax
  movl %eax, 0(%esi)
  movl %esi, %eax
  orl $6, %eax
  addl $4, %esi
  movl %eax, -12(%esp)

After movl $label_1, %eax in %eax: 0x565e6a30

Then we move %eax to the location pointed to by %esi, our heap pointer: movl %eax, 0(%esi)

Let's see if it's there. Here is what %esi contains:

(gdb) p/x $esi
$5 = 0xf7187010

And here is what is at 0xf7187010:

(gdb) x $esi
0xf7187010:     0x565e6a30

Our label.

Then %esi gets copied to %eax, because this pointer is the result of the evaluation.

movl %esi, %eax

Now we need to tag it.

Before, here are hex, binary, decimal representations:

0xf7187010	11110111000110000111000000010000	4145573904
0x565e6a30	01010110010111100110101000110000	1449028144

What we want to tag is 0xf7187010

Now, after executing orl $6, %eax:

(gdb) x $eax
0xf7187016:     0x00000000

That is

0xf7187016	11110111000110000111000000010110	4145573910

Our tag is 6, which is 110, so the tag worked.

Then we evaluate the next argument, the 2:

	movl $8, %eax
	movl %eax, -16(%esp)

Then we evaluate the closure we want to call:

	movl $label_0, %eax
	movl %eax, 0(%esi)
	movl %esi, %eax
	orl $6, %eax
	addl $4, %esi

After movl $label_0, %eax in %eax: 0x565e6a42

Then we also move %eax to the location pointed to by %esi, our heap pointer: movl %eax, 0(%esi)

Let's see if it's there. Here is what %esi contains:

(gdb) p/x $esi
$7 = 0xf7187014

And here is what is at 0xf7187014:

(gdb) x $esi
0xf7187014:     0x565e6a42

That is $label_0. Looks good. Move it to %eax:

(gdb) x $eax
0xf7187014:     0x565e6a42
0xf7187014	11110111000110000111000000010100	4145573908
0x565e6a42	01010110010111100110101001000010	1449028162

And now we tag it in %eax:

0xf7187016	11110111000110000111000000010110	4145573910
0x565e	    00000000000000000101011001011110	22110

Uh oh. The pointer has only been incremented by 2.

That means, when we then untag the pointer and do a subl $6, %eax, we decrease by 6, which doesn't get us to the correct original pointer.

Realization: the problem is that after we tag the first closure pointer, we only advance %esi by a single wordsize, 4. That is not enough to align the pointer at 8 bytes, which would allow us to use the lowest 3 bits for tagging.

What we need to do: instead of advancing %esi by a single word. We need to align it at double-word boundaries.

Yep, that's it. If we change the existing code from

(emit "orl $~a, %eax" object-tag-closure)
(emit "addl $~a, %esi" wordsize)

to this:

(emit "orl $~a, %eax" object-tag-closure)
;; Add 7+4 to pointer in %esi because
;; * 7 to align to multiple of 8
;; * 4 because that's the size of the label we stored and want to skip.
;; then bitwise-AND it with -8
(emit "addl $11, %esi")
(emit "andl $-8, %esi")

Then every test passes!

Thanks for tuning in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment