Skip to content

ImplementingPrimops

Felix S. Klock II edited this page Jul 28, 2013 · 1 revision

You implement an operation by registering with the operation's name a procedure that emits native code for that operation. The procedures have types something like this:


  car-primop : assembly-structure -> void

  vector-set!-primop : assembly-structure register register -> void

car has arity 1, so its single argument is in the result register ($r.result) and doesn't need to be given as a parameter. vector-set! takes 3 arguments, one in the result register and the other two registers given as parameters.

If you look at source:trunk/larceny_src/src/Asm/Sparc/sparcprim-part1.sch}}}, though, you see code like the following:


    (define-primop 'cons
      (lambda (as r)
        (emit-primop.4arg! as 'internal:cons $r.result r $r.result)))

The simple cons operation is implemented in terms of a more general internal:cons operation which takes its source and destination registers explicitly. internal:cons could never appear in an op2 instruction, but it is the kind of operation name that occurs inside of reg/op2/setreg instructions introduced by the peephole optimizer.

Sooner or later, after making your way through various abstractions, you get to the code that actually generates SparcInstructions.

The file source:trunk/larceny_src/src/Asm/Sparc/sparcasm2.sch defines a procedure for every Sparc instruction that the assembler uses. The procedures have a type something like this:


  sparc.addr : assembly-structure register register register -> void

  sparc.bne : assembly-structure label -> void

Let's look at the definition for internal:car (found in sparcprim-part2.sch):


  (define-primop 'internal:car
    (lambda (as src1 dest)
      (internal-primop-invariant2 'internal:car src1 dest)
      (if (not (unsafe-code))
          (emit-single-tagcheck-assert-reg! as
                                            $tag.pair-tag src1 #f $ex.car))
      (sparc.ldi as src1 (- $tag.pair-tag) dest)))

The procedure for internal:car expects both its source and destination registers to be mapped to real hardware registers so that it can use Sparc register instructions on MacScheme registers. Who maintains this invariant? Good question.

There's a helper procedure that emits code to check that the pointer has the right tag. Look at the design note for data representations at http://www.ccs.neu.edu/home/will/Larceny/notes/note2-repr.html. There are pointer tags and type tags (or subtags), two different things. Everything has a pointer tag, but only vector-likes, bytevector-likes, and procedures have typetags. Pairs don't--we just check the pointer tag. If we don't have a pair, we call the exception handler with the code $ex.car and it does something interesting.

We've got a tagged pointer to a pair. We want the first thing from it. If we tried to load directly from the tagged pointer, we'd probably get a bus error from a misaligned address. We need to to subtract the bitpattern of the tag away, leaving the untagged pointer. Fortunately, Sparc's [code ldi] instruction lets us specify an immediate offset. And with that, we're done. You just need to know what SparcInstructions you need to generate, and make sure you don't violate any LarcenyInvariants.

  • Always remember to take tags into account when doing loads and stores.
  • Refer to registers by name.
  • If you're using a Sparc register instructions on a MacScheme register, make sure it's a hardware register. Look for uses of force-hwreg!.
  • Refer to layout constants (tags, subtags, offsets, etc) by name as much as possible (look in source:trunk/larceny_src/src/Rts/layouts.cfg for names).
Clone this wiki locally