### -*-Midas-*- ### ### $Id: mc68k.m4,v 1.27 2000/12/05 21:23:50 cph Exp $ ### ### Copyright (c) 1989-2000 Massachusetts Institute of Technology ### ### This program is free software; you can redistribute it and/or ### modify it under the terms of the GNU General Public License as ### published by the Free Software Foundation; either version 2 of the ### License, or (at your option) any later version. ### ### This program is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ### General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with this program; if not, write to the Free Software ### Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ### #### 68K assembly language (HP/Motorola Syntax) part of the compiled #### code interface. See cmpint.txt, cmpint.c, cmpint-mc68k.h, and #### cmpgc.h for more documentation. #### #### NOTE: #### Assumptions: #### #### 1) All registers (except double floating point registers) and #### stack locations hold a C long object. #### #### 2) The C compiler divides registers into three groups: #### - Linkage registers, used for procedure calls and global #### references. On MC68K: a6, sp. #### - super temporaries, not preserved accross procedure calls and #### always usable. On MC68K: a0, a1, d0, d1 #### - preserved registers saved by the callee if they are written. #### On MC68K: all others. #### #### 3) Arguments, if passed on a stack, are popped by the caller #### or by the procedure return instruction (as on the VAX). Thus #### most "leaf" procedures need not worry about them. #### #### 4) There is a hardware or software maintained stack for #### control. The procedure calling sequence may leave return #### addresses in registers, but they must be saved somewhere for #### nested calls and recursive procedures. On MC68K: saved on #### the stack. #### #### 5) C procedures return long values in a super temporary #### register. Two word structures are returned in super temporary #### registers as well. On MC68K: d0 is used for long returns. #### Since there are two methods for returning structures on MC68K, #### there is a flag to choose a mechanism: #### o GCC returns two word structures in d0/d1 (set flag GCC in #### M4_MACHINE_SWITCHES in m.h) #### o Other compilers return the address of the structure in d0 #### o The HP compiler requires that the address of this structure #### be in a1 before the procedure is called (set flag HP in #### M4_MACHINE_SWITCHES in m.h) #### #### 6) Floating point registers are not preserved by this #### interface. The interface is only called from the Scheme #### interpreter, which does not use floating point data. Thus #### although the calling convention would require us to preserve #### them, they contain garbage. #### #### Compiled Scheme code uses the following register convention: #### - a7 (sp) contains the Scheme stack pointer, not the C stack #### pointer. #### - a6 (fp) contains a pointer to the Scheme interpreter's #### "register" block. This block contains the compiler's copy of #### MemTop, the interpreter's registers (val, env, exp, etc), #### temporary locations for compiled code, and the mechanism used #### to invoke the hooks in this file. #### - a5 contains the Scheme free pointer. #### - a4 contains the dynamic link when needed. #### - d7 contains the Scheme datum mask. #### - d6 is where Scheme compiled code returns values. #### #### All other registers are available to the compiler. A #### caller-saves convention is used, so the registers need not be #### preserved by subprocedures. #### Utility macros and definitions define(KEEP_HISTORY,0) # Debugging switch define(reference_external,`') # Declare desire to use an external define(extern_c_label,`_$1') # The actual reference define(define_c_label, ` global extern_c_label($1) extern_c_label($1):') define(define_debugging_label, ` global $1 $1:') # Call a SCHEME_UTILITY (see cmpint.c) and then dispatch to the # interface procedure requested with the data to be passed to the # procedure in d1. # # NOTE: Read introductory note about GCC and HP switches define(allocate_utility_result, `ifdef(`HP', `subq.l &8,%sp mov.l %sp,%a1', `')') define(utility_call, `jsr (%a0) # call C procedure ifdef(`HP', `lea eval(($1+2)*4)(%sp),%sp', `lea eval($1*4)(%sp),%sp') mov.l %d0,%a0 ifdef(`GCC', `', `mov.l 4(%a0),%d1 mov.l 0(%a0),%a0') jmp (%a0)') # Scheme object representation. Must match object.h define(HEX, `0x$1') define(TC_LENGTH, ifdef(`TYPE_CODE_LENGTH', TYPE_CODE_LENGTH, 6)) define(ADDRESS_MASK, eval(((2 ** (32 - TC_LENGTH)) - 1), 16)) define(TYPE_CODE_FACTOR, eval(2 ** (8 - TC_LENGTH))) define(TYPE_CODE_MASK, eval((256 - TYPE_CODE_FACTOR), 16)) define(CLEAR_TYPE_MASK, eval((TYPE_CODE_FACTOR - 1), 16)) define(TYPE_CODE_TO_BYTE, `$1*TYPE_CODE_FACTOR') define(TYPE_CODE_TO_OBJECT, `TYPE_CODE_TO_BYTE($1)*0x1000000') define(EXTRACT_TYPE_CODE, `ifelse(TC_LENGTH, 8, `mov.b $1,$2', `mov.b $1,$2 and.b &HEX(TYPE_CODE_MASK), $2')') define(COMPARE_TYPE_CODE, `cmp.b $1,&TYPE_CODE_TO_BYTE($2)') ### External conventions set regblock_memtop,0 # from const.h (* 4) set regblock_int_mask,4 set regblock_val,8 set regblock_stack_guard,44 set regblock_int_code,48 set address_mask,HEX(ADDRESS_MASK) # This must match the compiler (machin.scm) define(dlink, %a4) # Dynamic link register (contains a # pointer to a return address) define(rfree, %a5) # Free pointer define(regs, %a6) # Pointer to Registers[0] define(rmask, %d7) # Mask to clear type code define(rval,%d6) # Procedure value reference_external(Ext_Stack_Pointer) reference_external(Free) reference_external(Registers) # These must match the C compiler define(switch_to_scheme_registers, `mov.l %a6,(%sp) mov.l %sp,c_save_stack mov.l extern_c_label(Ext_Stack_Pointer),%sp mov.l extern_c_label(Free),rfree lea extern_c_label(Registers),regs mov.l &address_mask,rmask') define(switch_to_C_registers, `mov.l rfree,extern_c_label(Free) mov.l %sp,extern_c_label(Ext_Stack_Pointer) mov.l c_save_stack,%sp mov.l (%sp),%a6') ### ### Global data ### data define_debugging_label(c_save_stack) space 4 ifelse(KEEP_HISTORY, 1, `define_debugging_label(ring_pointer) long ring_block_1 define_debugging_label(ring_block_1) long ring_block_2 space 28 define_debugging_label(ring_block_2) long ring_block_3 space 28 define_debugging_label(ring_block_3) long ring_block_4 space 28 define_debugging_label(ring_block_4) long ring_block_5 space 28 define_debugging_label(ring_block_5) long ring_block_1 space 28') text ### Initialize the 68881 if present. define_c_label(interface_initialize) link %a6,&0 ifdef(`MC68881', `fmov.l &0x3480,%fpcr') unlk %a6 rts ### Callable by C conventions. Swaps to Scheme register set and jumps ### to the entry point specified by its only argument. define_c_label(C_to_interface) link %a6,&-44 movm.l %d2-%d7/%a2-%a5,4(%sp) mov.l 8(%a6),%a0 # Argument: entry point bra.b interface_to_scheme_internal ### Called by Scheme through a jump instruction in the register block. ### It expects an index in %d0, and 4 longword arguments in %d1-%d4 reference_external(utility_table) define_c_label(asm_scheme_to_interface) define_debugging_label(scheme_to_interface) ifelse(KEEP_HISTORY, 1, `lea ring_pointer,%a1 mov.l (%a1),%a0 mov.l (%a0),(%a1) mov.l %sp,4(%a0) mov.l %a5,8(%a0) mov.l %d0,12(%a0) mov.l %d1,16(%a0) mov.l %d2,20(%a0) mov.l %d3,24(%a0) mov.l %d4,28(%a0) cmp.l %sp,%a5 bgt.b scheme_to_interface_proceed nop define_debugging_label(scheme_to_interface_proceed)') mov.l rval,regblock_val(regs) switch_to_C_registers() allocate_utility_result() mov.l %d4,-(%sp) # Push arguments to scheme utility mov.l %d3,-(%sp) mov.l %d2,-(%sp) mov.l %d1,-(%sp) lea extern_c_label(utility_table),%a0 mov.l (0,%a0,%d0.w*4),%a0 # C-written Scheme utility utility_call(4) # 4 arguments ### The data in %d1 is the address of an entry point to invoke. define_c_label(interface_to_scheme) mov.l %d1,%a0 ### ### Enter the scheme compiled world. ### The value register is copied to %d0 because some utilities are ### expected to return their value there (this should be fixed), ### and it is stripped and placed in the dlink register since ### we may be returning after interrupting a procedure which ### needs this register. This should also be separated or handled ### inline. ### define_debugging_label(interface_to_scheme_internal) switch_to_scheme_registers() mov.l regblock_val(regs),rval mov.l rval,%d0 mov.l %d0,%d1 and.l rmask,%d1 mov.l %d1,dlink jmp (%a0) ### The data in %d1 is a return code (integer) to the interpreter. define_c_label(interface_to_C) mov.l %d1,%d0 # C return value location movm.l 4(%sp),%d2-%d7/%a2-%a5 unlk %a6 rts #### Optimized entry points ### Additional entry points that take care of common cases and are used to ### shorten code sequences. ### These are not strictly necessary, since the code sequences emitted by ### the compiler could use scheme_to_interface instead, but a few instructions ### are saved this way. ### Called by linker-generated trampolines to invoke the appropriate ### C-written handler. The return address on the stack is the address ### of the trampoline storage area, passed to the C handler as the ### first argument. ### IMPORTANT: ### All the asm_* routines are declared in cmpint-mc68k.h. ### New ones need to be declared there as well! define_c_label(asm_trampoline_to_interface) define_debugging_label(trampoline_to_interface) mov.l (%sp)+,%d1 bra scheme_to_interface ### Called by Scheme through a jump instruction in the register block. ### It is a special version of scheme_to_interface below, used when ### a return address is stored in the Scheme stack. define_c_label(asm_scheme_to_interface_jsr) define_debugging_label(scheme_to_interface_jsr) mov.l (%sp)+,%d1 # Return addr -> d1 addq.l &4,%d1 # Skip format info. bra scheme_to_interface define(define_interface_indirection, `define_c_label(asm_$1) movq &HEX($2),%d0 bra scheme_to_interface') define(define_interface_jsr_indirection, `define_c_label(asm_$1) movq &HEX($2),%d0 bra scheme_to_interface_jsr') define_interface_indirection(primitive_lexpr_apply,13) define_interface_indirection(error,15) define_interface_jsr_indirection(link,17) define_interface_indirection(interrupt_closure,18) define_interface_jsr_indirection(interrupt_procedure,1a) define_interface_jsr_indirection(interrupt_continuation,1b) define_interface_jsr_indirection(assignment_trap,1d) define_interface_jsr_indirection(reference_trap,1f) define_interface_jsr_indirection(safe_reference_trap,20) ### ### These are handled directly below. ### ### define_interface_indirection(generic_decrement,22) ### define_interface_indirection(generic_divide,23) ### define_interface_indirection(generic_equal,24) ### define_interface_indirection(generic_greater,25) ### define_interface_indirection(generic_increment,26) ### define_interface_indirection(generic_less,27) ### define_interface_indirection(generic_subtract,28) ### define_interface_indirection(generic_multiply,29) ### define_interface_indirection(generic_negative,2a) ### define_interface_indirection(generic_add,2b) ### define_interface_indirection(generic_positive,2c) ### define_interface_indirection(generic_zero,2d) ### define_interface_jsr_indirection(primitive_error,36) define_interface_indirection(generic_quotient,37) define_interface_indirection(generic_remainder,38) define_interface_indirection(generic_modulo,39) # Save an additional instruction here to load the dynamic link. define_c_label(asm_interrupt_dlink) mov.l dlink,%d2 # Dynamic link -> d2 movq &HEX(19),%d0 bra scheme_to_interface_jsr # Bum this one for speed. define_c_label(asm_primitive_apply) switch_to_C_registers() allocate_utility_result() mov.l %d1,-(%sp) # only one argument ifdef(`SUNASM', `lea extern_c_label(utility_table),%a0 mov.l HEX(12)*4(%a0),%a0', `mov.l extern_c_label(utility_table)+HEX(12)*4,%a0') utility_call(1) # one argument set tc_compiled_entry,HEX(28) set tc_flonum,HEX(06) set tc_fixnum,HEX(1A) set tc_manifest_nmv,HEX(27) set tc_false,HEX(0) set tc_true,HEX(8) set offset_apply,HEX(14) define(call_utility, `movq &offset_$1,%d0 bra scheme_to_interface') ### Called by Scheme when invoking an unknown procedure. ### Having this short sequence in assembly language avoids the C call ### in the common case where the procedure is compiled and the number ### of arguments is correct. ### The number of actual arguments is in d2, the procedure on top ### of the stack. define_c_label(asm_shortcircuit_apply) define_debugging_label(shortcircuit_apply) EXTRACT_TYPE_CODE((%sp),%d0) # Get procedure type mov.l (%sp)+,%d1 # Get procedure COMPARE_TYPE_CODE(%d0,tc_compiled_entry) bne.b shortcircuit_apply_1 mov.l %d1,%d3 # Extract entry point and.l rmask,%d3 mov.l %d3,%a0 mov.b -3(%a0),%d3 # Extract the frame size ext.w %d3 cmp.w %d2,%d3 # Is the frame size right? bne.b shortcircuit_apply_1 jmp (%a0) # Invoke define_debugging_label(shortcircuit_apply_1) call_utility(apply) ### Optimized versions of shortcircuit_apply for 0-7 arguments. define(define_apply_size_n, `define_c_label(asm_shortcircuit_apply_size_$1) define_debugging_label(shortcircuit_apply_size_$1) EXTRACT_TYPE_CODE((%sp),%d0) # Get procedure type mov.l (%sp)+,%d1 # Get procedure COMPARE_TYPE_CODE(%d0,tc_compiled_entry) bne.b shortcircuit_apply_size_$1_1 mov.l %d1,%d3 # Extract entry point and.l rmask,%d3 mov.l %d3,%a0 cmp.b -3(%a0),&$1 # Is the frame size right? bne.b shortcircuit_apply_size_$1_1 jmp (%a0) # Invoke define_debugging_label(shortcircuit_apply_size_$1_1) movq &$1,%d2 # initialize frame size call_utility(apply)') define_apply_size_n(1) define_apply_size_n(2) define_apply_size_n(3) define_apply_size_n(4) define_apply_size_n(5) define_apply_size_n(6) define_apply_size_n(7) define_apply_size_n(8) ### This utility depends on the C compiler preserving d2-d7 and a2-a7. ### It takes its parameters in d0 and d1, and returns its value in a0. define_c_label(asm_allocate_closure) switch_to_C_registers() mov.l %a1,-(%sp) # Preserve reg. mov.l %d1,-(%sp) # Preserve reg. mov.l %d0,-(%sp) # Push arg. jsr extern_c_label(allocate_closure) addq.l &4,%sp # Pop arg. mov.l %d0,%a0 # Return value mov.l (%sp)+,%d1 # Restore reg. mov.l (%sp)+,%a1 # Restore reg. switch_to_scheme_registers() rts ### These utilities improve the performance of floating point code ### significantly. ### Arguments on top of the stack followed by the return address. define_debugging_label(asm_generic_flonum_result) mov.l rfree,rval mov.l &TYPE_CODE_TO_OBJECT(tc_manifest_nmv)+2,(rfree)+ fmove.d %fp0,(rfree)+ or.l &TYPE_CODE_TO_OBJECT(tc_flonum),rval and.b &TYPE_CODE_TO_BYTE(1)-1,(%sp) rts define_debugging_label(asm_true_result) mov.l &TYPE_CODE_TO_OBJECT(tc_true),rval and.b &TYPE_CODE_TO_BYTE(1)-1,(%sp) rts define_debugging_label(asm_false_result) mov.l &TYPE_CODE_TO_OBJECT(tc_false),rval and.b &TYPE_CODE_TO_BYTE(1)-1,(%sp) rts define(define_generic_unary, `define_c_label(asm_generic_$1) EXTRACT_TYPE_CODE((%sp),%d0) # Get arg1s type COMPARE_TYPE_CODE(%d0,tc_flonum) bne.b asm_generic_$1_hook mov.l (%sp)+,%d0 # arg1 and.l rmask,%d0 mov.l %d0,%a0 fmove.d 4(%a0),%fp0 $3.b &1,%fp0 bra asm_generic_flonum_result asm_generic_$1_hook: movq &HEX($2),%d0 bra scheme_to_interface') define(define_generic_unary_predicate, `define_c_label(asm_generic_$1) EXTRACT_TYPE_CODE((%sp),%d0) # Get arg1s type COMPARE_TYPE_CODE(%d0,tc_flonum) bne.b asm_generic_$1_hook mov.l (%sp)+,%d0 # arg1 and.l rmask,%d0 mov.l %d0,%a0 fmove.d 4(%a0),%fp0 fb$3 asm_true_result bra asm_false_result asm_generic_$1_hook: movq &HEX($2),%d0 bra scheme_to_interface') define(define_generic_binary, `define_c_label(asm_generic_$1) EXTRACT_TYPE_CODE((%sp),%d0) # Get arg1s type EXTRACT_TYPE_CODE(4(%sp),%d1) # Get arg2s type mov.l (%sp),%d2 # arg1 mov.l 4(%sp),%d3 # arg2 and.l rmask,%d2 and.l rmask,%d3 mov.l %d2,%a0 mov.l %d3,%a1 COMPARE_TYPE_CODE(%d0,tc_flonum) bne.b asm_generic_$1_fix_flo COMPARE_TYPE_CODE(%d1,tc_flonum) bne.b asm_generic_$1_flo_fix fmove.d 4(%a0),%fp0 $3.d 4(%a1),%fp0 addq.l &8,%sp bra asm_generic_flonum_result asm_generic_$1_fix_flo: COMPARE_TYPE_CODE(%d0,tc_fixnum) bne.b asm_generic_$1_hook COMPARE_TYPE_CODE(%d1,tc_flonum) bne.b asm_generic_$1_hook lsl.l &TC_LENGTH,%d2 asr.l &TC_LENGTH,%d2 fmove.l %d2,%fp0 $3.d 4(%a1),%fp0 addq.l &8,%sp bra asm_generic_flonum_result asm_generic_$1_flo_fix: COMPARE_TYPE_CODE(%d1,tc_fixnum) bne.b asm_generic_$1_hook lsl.l &TC_LENGTH,%d3 asr.l &TC_LENGTH,%d3 fmove.d 4(%a0),%fp0 $3.l %d3,%fp0 addq.l &8,%sp bra asm_generic_flonum_result asm_generic_$1_hook: movq &HEX($2),%d0 bra scheme_to_interface') define(define_generic_binary_predicate, `define_c_label(asm_generic_$1) EXTRACT_TYPE_CODE((%sp),%d0) # Get arg1s type EXTRACT_TYPE_CODE(4(%sp),%d1) # Get arg2s type mov.l (%sp),%d2 # arg1 mov.l 4(%sp),%d3 # arg2 and.l rmask,%d2 and.l rmask,%d3 mov.l %d2,%a0 mov.l %d3,%a1 COMPARE_TYPE_CODE(%d0,tc_flonum) bne.b asm_generic_$1_fix_flo COMPARE_TYPE_CODE(%d1,tc_flonum) bne.b asm_generic_$1_flo_fix addq.l &8,%sp fmove.d 4(%a0),%fp0 fcmp.d %fp0,4(%a1) fb$3 asm_true_result bra asm_false_result asm_generic_$1_fix_flo: COMPARE_TYPE_CODE(%d0,tc_fixnum) bne.b asm_generic_$1_hook COMPARE_TYPE_CODE(%d1,tc_flonum) bne.b asm_generic_$1_hook addq.l &8,%sp lsl.l &TC_LENGTH,%d2 asr.l &TC_LENGTH,%d2 fmove.l %d2,%fp0 fcmp.d %fp0,4(%a1) fb$3 asm_true_result bra asm_false_result asm_generic_$1_flo_fix: COMPARE_TYPE_CODE(%d1,tc_fixnum) bne.b asm_generic_$1_hook addq.l &8,%sp lsl.l &TC_LENGTH,%d3 asr.l &TC_LENGTH,%d3 fmove.d 4(%a0),%fp0 fcmp.l %fp0,%d3 fb$3 asm_true_result bra asm_false_result asm_generic_$1_hook: movq &HEX($2),%d0 bra scheme_to_interface') define_generic_unary(decrement,22,fsub) define_generic_binary(divide,23,fdiv) define_generic_binary_predicate(equal,24,eq) define_generic_binary_predicate(greater,25,gt) define_generic_unary(increment,26,fadd) define_generic_binary_predicate(less,27,lt) define_generic_binary(subtract,28,fsub) define_generic_binary(multiply,29,fmul) define_generic_unary_predicate(negative,2a,lt) define_generic_binary(add,2b,fadd) define_generic_unary_predicate(positive,2c,gt) define_generic_unary_predicate(zero,2d,eq) ### Close-coded stack and interrupt check for use when stack checking ### is enabled. define_c_label(asm_stack_and_interrupt_check_12) mov.l &-12,-(%sp) bra.b stack_and_interrupt_check define_c_label(asm_stack_and_interrupt_check_14) mov.l &-14,-(%sp) bra.b stack_and_interrupt_check define_c_label(asm_stack_and_interrupt_check_18) mov.l &-18,-(%sp) bra.b stack_and_interrupt_check define_c_label(asm_stack_and_interrupt_check_22) mov.l &-22,-(%sp) bra.b stack_and_interrupt_check define_c_label(asm_stack_and_interrupt_check_24) mov.l &-24,-(%sp) # bra.b stack_and_interrupt_check ### On entry, 4(%sp) contains the resumption address, and 0(%sp) is ### the offset between the resumption address and the GC label ### address. define_debugging_label(stack_and_interrupt_check) ### If the Scheme stack pointer is <= Stack_Guard, then the stack has ### overflowed -- in which case we must signal a stack-overflow interrupt. cmp.l %sp,regblock_stack_guard(regs) bgt.b stack_and_interrupt_check_1 ### Set the stack-overflow interrupt bit. If the stack-overflow ### interrupt is disabled, skip forward to gc test. Otherwise, set ### MemTop to -1 and signal the interrupt. bset &0,regblock_int_code+3(regs) btst &0,regblock_int_mask+3(regs) beq.b stack_and_interrupt_check_1 mov.l &-1,regblock_memtop(regs) bra.b stack_and_interrupt_check_2 ### If (Free >= MemTop), signal an interrupt. stack_and_interrupt_check_1: cmp.l rfree,regblock_memtop(regs) bge.b stack_and_interrupt_check_2 ### No action necessary -- return to resumption address. addq.l &4,%sp rts ### Must signal the interrupt -- return to GC label instead. stack_and_interrupt_check_2: mov.l %d0,-(%sp) mov.l 4(%sp),%d0 add.l %d0,8(%sp) mov.l (%sp),%d0 addq.l &8,%sp rts ### Assembly-language implementation of SET-INTERRUPT-ENABLES! ### primitive. Argument appears at top of stack, return address below ### that. define_c_label(asm_set_interrupt_enables) define_debugging_label(set_interrupt_enables) # Return value is previous contents of mask register. mov.l regblock_int_mask(regs),rval or.l &TYPE_CODE_TO_OBJECT(tc_fixnum),rval mov.l (%sp)+,%d0 # get new interrupt mask and.l rmask,%d0 # strip fixnum type mov.l %d0,regblock_int_mask(regs) # store it in mask register # Setup compiled memtop register: -1 if pending interrupt, # Memtop if GC enabled, else Heap_Top. movq &-1,%d1 mov.l regblock_int_code(regs),%d2 and.l %d0,%d2 bne.b set_interrupt_enables_1 mov.l extern_c_label(MemTop),%d1 btst &2,%d0 bne.b set_interrupt_enables_1 mov.l extern_c_label(Heap_Top),%d1 set_interrupt_enables_1: mov.l %d1,regblock_memtop(regs) # Setup compiled stack_guard register: Stack_Guard if # stack-overflow enabled, else Stack_Bottom mov.l extern_c_label(Stack_Guard),%d1 btst &0,%d0 bne.b set_interrupt_enables_2 mov.l extern_c_label(Stack_Bottom),%d1 set_interrupt_enables_2: mov.l %d1,regblock_stack_guard(regs) mov.l (%sp)+,%d0 and.l rmask,%d0 mov.l %d0,%a0 jmp (%a0)