|
|
1.1 root 1: \section{Using [[MIPSCODER]] to implement a [[CMACHINE]]}
2:
3: <<*>>=
4: functor MipsCM(MipsC : MIPSCODER) : CMACHINE = struct
5:
6: open MipsC System.Tags
7:
8: <<utility functions>>
9:
10: <<immediate and register functions>>
11:
12: <<register definitions>>
13:
14: <<move>>
15: <<alignment, marks, and constants>>
16: <<labels>>
17: <<record manipulation>>
18: <<indexed fetch and store (byte)>>
19: <<indexed fetch and store (word)>>
20: <<arithmetic>>
21: <<shifts>>
22: <<arithmetic and shifts with overflow detection>>
23: <<bitwise operations>>
24: <<branches>>
25:
26: <<floating point>>
27:
28: <<memory check>>
29:
30: <<omitted functions>>
31:
32: val comment = M.comment
33:
34: (* +DEBUG *)
35: <<DEBUG code>>
36: (* -DEBUG *)
37:
38: end (* MipsCM *)
39:
40: @ The debugging code replaces possibly offensive functions with functions
41: that diagnose their own exceptions.
42: <<DEBUG code>>=
43: fun diag (s : string) f x =
44: f x handle e =>
45: (print "?exception "; print (System.exn_name e);
46: print " in mips."; print s; print "\n";
47: raise e)
48:
49: <<immediate and register functions>>=
50: val immed = Immed
51: fun isimmed(Immed i) = SOME i
52: | isimmed _ = NONE
53:
54: fun isreg(Direct(Reg i)) = SOME i | isreg _ = NONE
55: fun eqreg (a: EA) b = a=b
56:
57:
58: @ Here's what our register conventions are:
59: \input regs
60: <<register definitions>>=
61: val standardarg = Direct(Reg 2)
62: val standardcont = Direct(Reg 3)
63: val standardclosure = Direct(Reg 4)
64: val miscregs = map (Direct o Reg) [5,6,7,8,9,10,11,12,13,14,
65: 15,16,17,18,19]
66: val storeptr as Direct storeptr' = Direct(Reg 22)
67: val dataptr as Direct dataptr' = Direct(Reg 23)
68: val exnptr = Direct(Reg 30)
69:
70: (* internal use only *)
71: val my_arithtemp as Direct my_arithtemp'= Direct(Reg 20)
72: val my_ptrtemp as Direct my_ptrtemp' = Direct(Reg 21)
73:
74: (* exported for external use *)
75: val arithtemp as Direct arithtemp' = Direct(Reg 24)
76: val arithtemp2 as Direct arithtemp2'= Direct(Reg 25)
77:
78: <<move>>=
79: fun move (src,Direct dest) = M.move(src, dest)
80: | move _ = ErrorMsg.impossible "destination of move not register in mips"
81: <<alignment, marks, and constants>>=
82: val align = M.align
83: val mark = M.mark
84:
85: val emitlong = M.emitlong
86: val realconst = M.realconst
87: val emitstring = M.emitstring
88:
89: <<labels>>=
90: fun emitlab(i,Immedlab lab) = M.emitlab(i,lab)
91: | emitlab _ = ErrorMsg.impossible "bad emitlab arg in mips"
92: fun newlabel() = Immedlab(M.newlabel())
93: fun define (Immedlab lab) = M.define lab
94: | define _ = ErrorMsg.impossible "bad define arg in mips"
95: <<DEBUG code>>=
96: val emitlab = diag "emitlab" emitlab
97: val define = diag "define" define
98:
99:
100: @ We only ever put the address of a newly created record into a register.
101: If I make this out correctly, the first word on the list of
102: values [[vl]] is actually a descriptor.
103: BUGS: The original routine put the address of the descriptor
104: into [[z]].
105: What needs to go into [[z]] is the address of the first word in the record.
106: We can get this by adding 4 to the [[dataptr']].
107: <<record manipulation>>=
108: fun record(vl, Direct z) =
109: let open CPS
110: val len = List.length vl
111: fun f(i,nil) = ()
112: | f(i,(r, SELp(j,p))::rest) = (* follow ptrs to get the item *)
113: (M.lw(my_ptrtemp', r, j*4); f(i,(my_ptrtemp,p)::rest))
114: | f(i,(Direct r,OFFp 0)::rest) = (* simple store, last first *)
115: (M.sw(r, dataptr, i*4); f(i-1,rest))
116: | f(i,(Direct r, OFFp j)::rest) =
117: (M.add(r, Immed(4*j), my_ptrtemp');
118: f(i,(my_ptrtemp,OFFp 0)::rest))
119: | f(i,(ea,p)::rest) = (* convert to register-based *)
120: (M.move(ea, my_ptrtemp'); f(i,(my_ptrtemp,p)::rest))
121: in f(len - 1, rev vl); (* store first word in [[0(dataptr')]] *)
122: M.add(dataptr', Immed 4, z);
123: M.add(dataptr', Immed(4*len), dataptr')
124: end
125: | record _ = ErrorMsg.impossible "result of record not register in mips"
126:
127: fun select(i, r, Direct s) = M.lw(s, r, i*4)
128: | select _ = ErrorMsg.impossible "result of select not register in mips"
129:
130: fun offset(i, Direct r, Direct s) = M.add(r,Immed(i*4), s)
131: | offset _ = ErrorMsg.impossible "nonregister arg to offset in mips"
132: <<DEBUG code>>=
133: val record = diag "record" record
134: val select = diag "select" select
135: val offset = diag "offset" offset
136:
137: @ For the indexed fetch and store, arithtemp is {\em not} tagged---the
138: tags are removed at a higher level (in {\tt generic.sml}).
139: These could be made faster for the case when they're called with immediate
140: constants as [[x]].
141: <<indexed fetch and store (byte)>>=
142: (* fetchindexb(x,y) fetches a byte: y <- mem[x+arithtemp]
143: y cannot be arithtemp *)
144: fun fetchindexb(x,Direct y) =
145: (M.add(arithtemp',x,my_arithtemp');
146: M.lbu(y,my_arithtemp,0))
147: | fetchindexb _ = ErrorMsg.impossible "fetchb result not register in mips"
148:
149: (* storeindexb(x,y) stores a byte: mem[y+arithtemp] <- x; *)
150: fun storeindexb(Direct x,y) =
151: (M.add(arithtemp',y,my_arithtemp');
152: M.sb(x,my_arithtemp,0))
153: | storeindexb _ = ErrorMsg.impossible "storeb arg not register in mips"
154:
155: (* jmpindexb(x) pc <- (x+arithtemp) *)
156: fun jmpindexb x = (M.add(arithtemp',x,my_arithtemp');
157: M.jump(my_arithtemp'))
158:
159: <<DEBUG code>>=
160: val fetchindexb = diag "fetchindexb" fetchindexb
161: val storeindexb = diag "storeindexb" storeindexb
162: val jmpindexb = diag "jmpindexb" jmpindexb
163:
164:
165: @ Here it looks like [[z]] is a tagged integer number of words,
166: so that [[2*(z-1)]] converts to the appropriate byte offset.
167: But I'm just guessing.
168: In any case, it saves an instruction to compute [[2*z]] (actually [[z+z]])
169: and
170: load (or store) with offset [[~2]].
171:
172: Anything stored with [[storeindexl]] is being put into an array, so it
173: is safe to treat it as a pointer.
174: <<indexed fetch and store (word)>>=
175: (* fetchindexl(x,y,z) fetches a word: y <- mem[x+2*(z-1)] *)
176: (* storeindexl(x,y,z) stores a word: mem[y+2*(z-1)] <- x *)
177:
178: fun fetchindexl(x,Direct y, Direct z) =
179: (M.sll(Immed 1,z,my_arithtemp');
180: M.add(my_arithtemp',x,my_arithtemp');
181: M.lw(y, my_arithtemp,~2))
182: | fetchindexl(x,Direct y, Immed z) = M.lw(y, x, z+z-2)
183: | fetchindexl _ = ErrorMsg.impossible "fetchl result not register in mips"
184:
185: fun storeindexl(Direct x,y, Immed 1) = M.sw(x,y,0)
186: | storeindexl(Direct x,y,Direct z) =
187: (M.sll(Immed 1,z,my_arithtemp');
188: M.add(my_arithtemp',y,my_arithtemp');
189: M.sw(x, my_arithtemp,~2))
190: | storeindexl(Direct x,y,Immed z) = M.sw(x,y,z+z-2)
191:
192: | storeindexl(Direct _,_,Immedlab _) =
193: ErrorMsg.impossible "storeindexl(Direct _,_,Immedlab _) in mips"
194:
195: | storeindexl(Immedlab label,y,z) =
196: (M.move(Immedlab label,my_ptrtemp');
197: storeindexl(my_ptrtemp,y,z))
198:
199: | storeindexl(Immed constant,y,offset) =
200: (M.move(Immed constant,my_ptrtemp');
201: storeindexl(my_ptrtemp,y,offset))
202:
203: <<DEBUG code>>=
204: val fetchindexl = diag "fetchindexl" fetchindexl
205: val storeindexl = diag "storeindexl" storeindexl
206:
207:
208: @ The function [[three]] makes commutative three-operand
209: instructions easier to call.
210: All three operands become [[EA]]s, and it is enough if either of the
211: first two operands is a register.
212: <<utility functions>>=
213: fun three f (Direct x, ea, Direct y) = f(x,ea,y)
214: | three f (ea, Direct x, Direct y) = f(x,ea,y)
215: | three f _ =ErrorMsg.impossible "neither arg to three f is register in mips"
216:
217: @ I assume that shifts are only ever done on arithmetic quantities,
218: not pointers, so that I am justified in using [[my_arithtemp']] to
219: store intermediate values. This is consistent with being unwilling
220: to shift things matching [[Immedlab _]].
221: Appel agrees that pointers aren't shifted, as far as he can remember.
222: <<shifts>>=
223: fun ashr(shamt, Direct op1, Direct result) = M.sra(shamt,op1,result)
224: | ashr(shamt, Immed op1, Direct result) =
225: (M.move(Immed op1,my_arithtemp'); M.sra(shamt,my_arithtemp',result))
226: | ashr _ = ErrorMsg.impossible "ashr args don't match in mips"
227: fun ashl(shamt, Direct op1, Direct result) = M.sll(shamt,op1,result)
228: | ashl(shamt, Immed op1, Direct result) =
229: (M.move(Immed op1,my_arithtemp'); M.sll(shamt,my_arithtemp',result))
230: | ashl _ = ErrorMsg.impossible "ashl args don't match in mips"
231: <<DEBUG code>>=
232: val ashr = diag "ashr" ashr
233: val ashl = diag "ashl" ashl
234:
235: <<bitwise operations>>=
236: val orb = three M.or
237: val andb = three M.and'
238: fun notb (a,b) = subl3(a, Immed ~1, b) (* ~1 - a == one's complement *)
239: val xorb = three M.xor
240: <<DEBUG code>>=
241: val orb = diag "orb" orb
242: val andb = diag "andb" andb
243: val notb = diag "notb" notb
244: val xorb = diag "xorb" xorb
245:
246:
247: @ Subtraction may appear a bit odd.
248: The MIPS machine instruction and [[MIPSCODER.sub]] both subtract
249: their second operand from their first operand.
250: The VAX machine instruction and [[CMACHINE.subl3]] both subtract
251: their first operand from their second operand.
252: This will certainly lead to endless confusion.
253: <<arithmetic>>=
254: val addl3 = three M.add
255:
256: fun subl3(Immed k, x, y) = addl3(x, Immed(~k), y)
257: | subl3(Direct x, Direct y, Direct z) = M.sub(y,x,z)
258: | subl3(x, Immed k, dest) =
259: (M.move(Immed k, my_arithtemp');
260: subl3(x, my_arithtemp, dest))
261: | subl3 _ = ErrorMsg.impossible "subl3 args don't match in mips"
262:
263: @ We assume that any quantities being multiplied are arithmetic
264: quantities, not pointers.
265: <<arithmetic>>=
266: fun mull2(Direct x, Direct y) = M.mult(y,x,y)
267: | mull2(Immed x, Direct y) = (M.move(Immed x,my_arithtemp');
268: M.mult(y,my_arithtemp',y))
269: | mull2 _ = ErrorMsg.impossible "mull2 args don't match in mips"
270: fun divl2(Direct x, Direct y) = M.div(y,x,y)
271: | divl2(Immed x, Direct y) = (M.move(Immed x,my_arithtemp');
272: M.div(y,my_arithtemp',y))
273: | divl2 _ = ErrorMsg.impossible "divl2 args don't match in mips"
274:
275: <<DEBUG code>>=
276: val addl3 = diag "addl3" addl3
277: val subl3 = diag "subl3" subl3
278: val mull2 = diag "mull2" mull2
279: val divl2 = diag "divl2" divl2
280:
281:
282: @ The Mips hardware detects two's complement integer overflow on
283: add and subtract instructions only.
284: The exception is not maskable (see the Mips book, page 5-18).
285: At the moment we don't implement any overflow detection for multiplications
286: or for left shifts.
287: This has consequences only for coping with real constants and for
288: compiling user programs.
289: <<arithmetic and shifts with overflow detection>>=
290: val addl3t = addl3
291: val subl3t = subl3
292: @ The Mips multiplies two 32-bit quantities to get a 64-bit result.
293: That result fits in 32 bits if and only if the high-order word is zero or
294: negative one, and it has the same sign as the low order word.
295: Thus, we can add the sign bit of the low order word to the high order
296: word, and we have overflow if and only if the result is nonzero.
297: <<arithmetic and shifts with overflow detection>>=
298: fun mull2t(x,y as Direct y') =
299: let val ok = M.newlabel()
300: in mull2(x,y);
301: M.mfhi(my_arithtemp');
302: M.slt(y',Direct (Reg 0),my_ptrtemp'); (* 0 or 1 OK in pointer *)
303: M.add(my_arithtemp',my_ptrtemp,my_arithtemp');
304: M.beq(true,my_arithtemp',Reg 0,ok); (* OK if not overflow *)
305: M.lui(my_arithtemp',32767);
306: M.add(my_arithtemp',my_arithtemp,my_arithtemp'); (* overflows *)
307: M.define(ok)
308: end
309: | mull2t _ = ErrorMsg.impossible "result of mull2t not register in mips"
310:
311: <<DEBUG code>>=
312: val addl3t = diag "addl3t" addl3t
313: val subl3t = diag "subl3t" subl3t
314: val mull2t = diag "mull2t" mull2t
315: val ashlt = diag "ashlt" ashlt
316:
317:
318: @ We hack [[ibranch]] to make sure it will only reverse once.
319: It's easier than thinking.
320: <<branches>>=
321: datatype condition = NEQ | EQL | LEQ | GEQ | LSS | GTR
322: local
323: fun makeibranch reverse =
324: let
325: fun ibranch (cond, Immed a, Immed b, Immedlab label) =
326: if (case cond of EQL => a=b | NEQ => a<>b | LSS => a<b |
327: LEQ => a<=b | GTR => a>b | GEQ => a>=b)
328: then M.beq(true,Reg 0, Reg 0, label) else ()
329: | ibranch (NEQ, Direct r, Direct s, Immedlab label) =
330: M.beq(false, r, s, label)
331: | ibranch (NEQ, Direct r, x, Immedlab label) =
332: (M.move(x, my_arithtemp');
333: M.beq(false, r, my_arithtemp', label))
334: | ibranch (EQL, Direct r, Direct s, Immedlab label) =
335: M.beq(true, r, s, label)
336: | ibranch (EQL, Direct r, x, Immedlab label) =
337: (M.move(x, my_arithtemp');
338: M.beq(true, r, my_arithtemp', label))
339: | ibranch (LSS, Direct r, x, Immedlab lab) =
340: (M.slt(r,x,my_arithtemp');
341: M.beq(false,Reg 0, my_arithtemp',lab))
342: | ibranch (GEQ, Direct r, x, Immedlab lab) =
343: (M.slt(r,x,my_arithtemp');
344: M.beq(true,Reg 0, my_arithtemp',lab))
345: | ibranch (GTR, x, Direct r, Immedlab lab) =
346: (M.slt(r,x,my_arithtemp');
347: M.beq(false,Reg 0, my_arithtemp',lab))
348: | ibranch (LEQ, x, Direct r, Immedlab lab) =
349: (M.slt(r,x,my_arithtemp');
350: M.beq(true,Reg 0, my_arithtemp',lab))
351: (* These two cases added to prevent infinite reversal *)
352: | ibranch (GTR, Direct r, x, Immedlab lab) =
353: (M.move(x, my_arithtemp');
354: M.slt(my_arithtemp',Direct r,my_arithtemp');
355: M.beq(false,Reg 0,my_arithtemp',lab))
356: | ibranch (LEQ, Direct r, x, Immedlab lab) =
357: (M.move(x, my_arithtemp');
358: M.slt(my_arithtemp',Direct r,my_arithtemp');
359: M.beq(true,Reg 0,my_arithtemp',lab))
360: | ibranch (_, Immedlab _, Immedlab _, _) =
361: ErrorMsg.impossible "bad ibranch args 1 in mips"
362: | ibranch (_, Immedlab _, _, _) =
363: ErrorMsg.impossible "bad ibranch args 1a in mips"
364: | ibranch (_, _, Immedlab _, _) =
365: ErrorMsg.impossible "bad ibranch args 1b in mips"
366: | ibranch (_, _, _, Direct _) =
367: ErrorMsg.impossible "bad ibranch args 2 in mips"
368: | ibranch (_, _, _, Immed _) =
369: ErrorMsg.impossible "bad ibranch args 3 in mips"
370: | ibranch (cond, x, y, l) =
371: let fun rev LEQ = GEQ
372: | rev GEQ = LEQ
373: | rev LSS = GTR
374: | rev GTR = LSS
375: | rev NEQ = NEQ
376: | rev EQL = EQL
377: in if reverse then (makeibranch false) (rev cond, y,x,l)
378: else ErrorMsg.impossible "infinite ibranch reversal in mips"
379:
380: end
381: in ibranch
382: end
383: in
384: val ibranch = makeibranch true
385: end
386:
387: <<branches>>=
388: fun jmp (Direct r) = M.jump(r)
389: | jmp (Immedlab lab) = M.beq(true,Reg 0,Reg 0,lab)
390: | jmp (Immed i) = ErrorMsg.impossible "jmp (Immed i) in mips"
391:
392:
393: (* branch on bit set *)
394: fun bbs (Immed k, Direct y, Immedlab label) =
395: (M.and'(y,Immed (Bits.lshift(1,k)),my_arithtemp');
396: M.beq(false,my_arithtemp',Reg 0,label))
397: | bbs _ = ErrorMsg.impossible "bbs args don't match in mips"
398:
399: <<DEBUG code>>=
400: val ibranch = diag "ibranch" ibranch
401: val jmp = diag "jmp" jmp
402: val bbs = diag "bbs" bbs
403:
404: @ We decided not to include floating point registers in our galaxy of
405: effective addresses.
406: This means that floating point registers are used only at this level, and
407: only to contain intermediate results.
408: All operands and final results will be stored in memory, in the usual
409: ML format (i.e. as 8-byte strings).
410:
411: In fact, we can be much more strict than that, and claim that
412: all floating point operands will live in FPR0 and FPR2, and that all
413: results will appear in FPR0.
414:
415: We don't make a distinction between general-purpose and floating point
416: registers; it's up to the instructions to know the difference.
417: <<floating point>>=
418: val floatop1 = Reg 0
419: val floatop2 = Reg 2
420: val floatresult = Reg 0
421:
422: @ One very common operation is to take the result of a floating point
423: operation and put it into a fresh record, newly allocated on the heap.
424: This operation is traditionally called [[finish_real]], and it takes one
425: argument, the destination register for the new value.
426: All real values on the heap are labelled as 8-byte strings.
427: To store a floating point, we store the least significant
428: word in the lower address, but we store the most significant word
429: first, in case that triggers a garbage collection.
430: <<floating point>>=
431: val real_tag = Immed(8*System.Tags.power_tags + System.Tags.tag_string)
432:
433: fun store_float(Reg n,ea,offset) =
434: if n mod 2 <> 0 then ErrorMsg.impossible "bad float reg in mips"
435: else (M.swc1(Reg (n+1),ea,offset+4);M.swc1(Reg n,ea,offset))
436:
437: fun finish_real (Direct result) = (
438: store_float(floatresult,dataptr,4);
439: M.move(real_tag,my_arithtemp');
440: M.sw(my_arithtemp',dataptr,0);
441: M.add(dataptr',Immed 4,result);
442: M.add(dataptr',Immed 12,dataptr'))
443: | finish_real _ =
444: ErrorMsg.impossible "ptr to result of real operation not register in mips"
445:
446: @ Loading a floating point quantity is analogous.
447: <<floating point>>=
448: fun load_float(Reg dest,src,offset) =
449: if dest mod 2 <> 0 then ErrorMsg.impossible "bad float reg in mips"
450: else (M.lwc1(Reg dest,src,offset); M.lwc1(Reg (dest+1),src,offset+4))
451:
452: @ Now we can do a general two- and three-operand floating point operationa.
453: The only parameter is the function in [[MipsCoder]] that
454: emits the floating point register operation.
455: <<floating point>>=
456: fun two_float instruction (op1,result) = (
457: load_float(floatop1,op1,0);
458: instruction(floatop1,floatresult);
459: finish_real(result))
460:
461: fun three_float instruction (op1,op2,result) = (
462: load_float(floatop1,op1,0);
463: load_float(floatop2,op2,0);
464: instruction(floatop1,floatop2,floatresult);
465: finish_real(result))
466:
467: @ That takes care of everything except branch
468: <<floating point>>=
469: val mnegg = two_float M.neg_double
470: val mulg3 = three_float M.mul_double
471: val divg3 = three_float M.div_double
472: val addg3 = three_float M.add_double
473: val subg3 = three_float M.sub_double
474:
475:
476: @ The Mips doesn't provide all six comparisons in hardware, so the
477: next function does the comparison using only less than and equal.
478: The result tells [[bcop1]] whether to branch on condition true
479: or condition false.
480: <<floating point compare>>=
481: fun compare(LSS,op1,op2) = (M.slt_double(op1,op2); true)
482: | compare(GEQ,op1,op2) = (M.slt_double(op1,op2); false)
483: | compare(EQL,op1,op2) = (M.seq_double(op1,op2); true)
484: | compare(NEQ,op1,op2) = (M.seq_double(op1,op2); false)
485: | compare(LEQ,op1,op2) = compare(GEQ,op2,op1)
486: | compare(GTR,op1,op2) = compare(LSS,op2,op1)
487: <<floating point>>=
488: local
489: <<floating point compare>>
490: in
491: fun gbranch (cond, op1, op2, Immedlab label) = (
492: load_float(floatop1,op1,0);
493: load_float(floatop2,op2,0);
494: M.bcop1(compare(cond,floatop1,floatop2),label))
495: | gbranch _ = ErrorMsg.impossible "insane gbranch target in mips.nw"
496: end
497:
498:
499: @ When a function begins execution, it checks to make sure there is sufficient
500: memory available that it can do all its allocation.
501: generic does this by calling [[checkLimit : int -> unit]].
502: At the moment, we implement this check by doing a store,
503: taking advantage of the virtual memory hardware, which will cause an exception
504: if there's not enough memory.
505: Later we will replace this store with a check against a limit register,
506: which will avoid virtual memory hacking and which will have advantages
507: for concurrency.
508: <<memory check>>=
509: fun checkLimit max_allocation = M.sw(Reg 0, dataptr, max_allocation-4)
510: (* store zero in last location to be used *)
511:
512: @ These two functions have null implementations.
513: [[beginStdFn]] is necessary only on the SPARC, since that machine needs to get
514: its program counter, and it is awkward to do so in the middle of a function.
515:
516: [[profile]] is a mysterious relic.
517: <<omitted functions>>=
518: fun beginStdFn _ = () (* do nothing, just like the Vax *)
519:
520: fun profile(i,incr) = ()
521:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.