## Jitter specification for the JitterLisp VM. ## Copyright (C) 2017, 2018, 2020 Luca Saiu ## Written by Luca Saiu ## This file is part of JitterLisp, distributed as an example along with Jitter ## under the same license. ## Jitter 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 3 of the License, or ## (at your option) any later version. ## Jitter 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 Jitter. If not, see . ## Global configuration. ################################################################# vm set prefix "jitterlispvm" end ## Stacks and register classes. ################################################################# stack m long-name "mainstack" c-element-type "jitterlisp_object" c-initial-value "JITTERLISP_UNDEFINED" tos-optimized end stack t long-name "returnstack" c-element-type "jitterlisp_object" c-initial-value "JITTERLISP_UNDEFINED" non-tos-optimized end register-class r c-type "jitterlisp_object" c-initial-value "JITTERLISP_UNDEFINED" fast-register-no 0 #2 end ## Functions and globals to wrap. ################################################################# wrapped-functions # Libc cunctions which may be implicitly called by compiled code, for # example when passing parameters. # FIXME: no, GCC is not stupid and generates these directly in its # intermediate representation. The hack doesn't work here. # Temporary/debugging stuff. debug_pointer # printf jitterlisp_print # Calling interpreted closures from compiled code. jitterlisp_call_interpreted # These are needed in operations, as per jitterlisp-operations.h . Only # the functions we call *directly* thru instructions need to be wrapped: # low-priority primitives, not worth optimizing, are all called via the # "primitive" VM instruction which invokes a function pointer given as an # instruction argument. # (None yet.) # [FIXME: I'll need some function related to eval, to call interpreted # code from VM code] # Some primitives, notably cons , allocate heap memory from VM instructions. jitterlisp_allocate # Erroring out from instructions. jitterlisp_fail_from_vm jitterlispvm_error_invalid_primitive_argument_type_unfriendly jitterlispvm_error_invalid_primitive_argument_type_friendly end wrapped-globals stack_printf_format_string end early-header-c code # include "jitterlisp.h" end end initialization-c code end end finalization-c code end end early-c code # include end end late-c code __attribute__ ((unused)) void debug_pointer (jitter_int index, const void *pointer) { return; printf ("[DEBUG: %lli %p]\n", (long long) index, pointer); fflush (stdout); } /* Open an argument-type-checking sequence, for the given number of arguments to be read from the main stack. This is a helper macro for JITTERLISPVM_CHECK_TYPES_* . */ #define JITTERLISPVM_BEGIN_CHECK_TYPES_(_jitterlisp_vm_in_arity) \ JITTER_BEGIN_ \ int _jitterlispvm_arg_no = (_jitterlisp_vm_in_arity); \ int _jitterlispvm_arg_index __attribute__ ((unused)) = 0; \ int _jitterlispvm_arg_depth __attribute__ ((unused)) \ = _jitterlispvm_arg_no - 1; /* Check the next argument in the sequence, failing if its type doesn't match the given uppercase suffix. Do nothing if compiling an unsafe JitterLisp. */ #ifdef JITTERLISP_UNSAFE # define JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type, \ _jitterlisp_error_fast_label) \ { /* Do nothing. */ } #else # define JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type, \ _jitterlisp_error_fast_label) \ { \ /* Branch-fast to the given label if the next object type is \ not the required one. */ \ JITTER_CONCATENATE_TWO (JITTERLISP_BRANCH_FAST_UNLESS_, \ _jitterlisp_uppercase_type) \ (JITTER_AT_DEPTH_MAINSTACK(_jitterlispvm_arg_depth), \ _jitterlisp_error_fast_label); \ _jitterlispvm_arg_depth --; \ _jitterlispvm_arg_index ++; \ } #endif // #ifdef JITTERLISP_UNSAFE /* Close an argument-type-checking sequence. This is a helper macro for JITTERLISPVM_CHECK_TYPES_* . */ #define JITTERLISPVM_END_CHECK_TYPES_ \ JITTER_END_ /* For in-arity N, check that the topmost N arguments have the given N types expressed as uppercase suffixes, respectively. This functionality is provided for a few common values of N . */ #define JITTERLISPVM_CHECK_TYPES_1(_jitterlisp_uppercase_type_0, \ _jitterlisp_error_fast_label) \ JITTERLISPVM_BEGIN_CHECK_TYPES_(1) \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_0, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_END_CHECK_TYPES_ #define JITTERLISPVM_CHECK_TYPES_2(_jitterlisp_uppercase_type_0, \ _jitterlisp_uppercase_type_1, \ _jitterlisp_error_fast_label) \ JITTERLISPVM_BEGIN_CHECK_TYPES_(2) \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_0, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_1, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_END_CHECK_TYPES_ #define JITTERLISPVM_CHECK_TYPES_3(_jitterlisp_uppercase_type_0, \ _jitterlisp_uppercase_type_1, \ _jitterlisp_uppercase_type_2, \ _jitterlisp_error_fast_label) \ JITTERLISPVM_BEGIN_CHECK_TYPES_(3) \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_0, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_1, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_2, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_END_CHECK_TYPES_ #define JITTERLISPVM_CHECK_TYPES_4(_jitterlisp_uppercase_type_0, \ _jitterlisp_uppercase_type_1, \ _jitterlisp_uppercase_type_2, \ _jitterlisp_uppercase_type_3, \ _jitterlisp_error_fast_label) \ JITTERLISPVM_BEGIN_CHECK_TYPES_(4) \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_0, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_1, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_2, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_CHECK_NEXT_TYPE_(_jitterlisp_uppercase_type_3, \ _jitterlisp_error_fast_label); \ JITTERLISPVM_END_CHECK_TYPES_ #if 0 # define JITTERLISPVM_ERROR_INVALID_PRIMITIVE_ARGUMENT_TYPE_ \ jitterlispvm_error_invalid_primitive_argument_type_unfriendly () #else # define JITTERLISPVM_ERROR_INVALID_PRIMITIVE_ARGUMENT_TYPE_ \ jitterlispvm_error_invalid_primitive_argument_type_friendly \ (JITTER_SPECIALIZED_INSTRUCTION_OPCODE, \ _jitterlispvm_arg_index, \ JITTER_AT_DEPTH_MAINSTACK(_jitterlispvm_arg_depth)) #endif // Unfortunately the attribute is invisible across the wrapper. // This is convenient for debugging, but I don't want to use it in production: // passing parameters at every call makes the code much bigger, even if the // call is not executed. __attribute__ ((noreturn, unused)) static void jitterlispvm_error_invalid_primitive_argument_type_friendly (int sins_opcode, int arg_index, jitterlisp_object o) { /* Print information about the argument and its hex representation: don't dare yet to print it as a Lisp object, as the printer might crash if there is a bug and the object is invalid. */ char buffer [1000]; printf (buffer, "%s: About the %i-th (0-based) primitive argument %p\n", jitterlispvm_specialized_instruction_names [sins_opcode], arg_index, (void *) o); jitterlisp_print_error_char_star (buffer); /* This shouldn't be needed on GNU, but flush the output so that we can be sure that the previous line is visible before we crash. */ jitter_print_flush (jitterlisp_print_context); /* Okay, now we can print in in Lisp. */ jitterlisp_print_error_char_star (" (in Lisp "); jitterlisp_print_error (o); jitterlisp_print_error_char_star ("):\n"); jitterlisp_error_cloned ("invalid primitive argument type"); } __attribute__ ((noreturn, unused)) static void jitterlispvm_error_invalid_primitive_argument_type_unfriendly (void) { jitterlisp_error_cloned ("invalid primitive argument type"); } static void jitterlisp_fail_from_vm (void) { jitterlisp_error_cloned ("unspecified error raised by VM code"); } static const char *stack_printf_format_string __attribute__ ((unused)) = "%" JITTER_PRIi "\n"; end end ## Custom literal argument printer. ################################################################# printer-c code /* Not really needed. The printer is jitterlisp_print. */ end end ## Debugging code for instructions. ################################################################# early-c code end end late-c #initialization-c code end end instruction-beginning-c code end end instruction-end-c code end end ## User-defined state fields (scratch). ################################################################# early-header-c code /* If enabled, use my nonworking heap allocation stub. This is useful to me, for playing with the still non-existent Jitter garbage collector and reason about its API. */ //#define JITTER_GC_STUB end end late-header-c code end end late-c code end end state-struct-runtime-c code #ifdef JITTER_GC_STUB /* A pointer to the next free byte in the nursery. Untagged. */ char *allocation_next; /* The nursery allocation limit, untagged -- which is to say, the maximum valid address in the nursery plus 1. Notice that it is correct, is slightly conservative, to compare even a tagged pointer against this in an expression like new_tagged_pointer < allocation_limit , since a tagged pointer is always greater than or equal to its untagged counterpart. */ char *allocation_limit; #endif // #ifdef JITTER_GC_STUB end end state-initialization-c code #ifdef JITTER_GC_STUB /* Initialize the next pointer and the limit pointer to refer to a fixed-size nursery. There is no real GC yet, so when next hits limit there will be a failure; still, allocation should work up to that point. */ size_t nursery_size = 1024 * 1024 * 10; char *nursery = jitter_xmalloc (nursery_size); jitter_state_runtime->allocation_next = nursery; jitter_state_runtime->allocation_limit = nursery + nursery_size; #endif // #ifdef JITTER_GC_STUB end end ## Instructions. ################################################################# instruction nop () code end end instruction dup () code JITTER_DUP_MAINSTACK(); end end instruction drop () code JITTER_DROP_MAINSTACK(); end end instruction nip () code JITTER_NIP_MAINSTACK(); end end instruction nip-drop () code JITTER_NIP_MAINSTACK(); JITTER_DROP_MAINSTACK(); end end instruction drop-nip () code JITTER_DROP_MAINSTACK(); JITTER_NIP_MAINSTACK(); end end # FIXME: make a one-argument version generalizing these. These zero-argument # versions are useful to generate from rewrite rules right now, before Jitter # support is ready. # Each of these VM instructions compiles to just one fast hardware instruction, # decrementing a register by a constant without touching memory. instruction nip-two () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); end end instruction nip-three () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); end end instruction nip-four () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); end end instruction nip-five () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); end end instruction nip-six () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); end end # Each of these has the same cost of a single drop. # FIXME: generalize into a unary instruction when Jitter rewrite rules become # expressive enough to obtain these by rewriting. instruction nip-two-drop () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_DROP_MAINSTACK(); end end instruction nip-three-drop () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_DROP_MAINSTACK(); end end instruction nip-four-drop () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_DROP_MAINSTACK(); end end instruction nip-five-drop () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_DROP_MAINSTACK(); end end instruction nip-six-drop () code JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_DROP_MAINSTACK(); end end # FIXME: is this ever needed? instruction push-unspecified () code JITTER_PUSH_UNSPECIFIED_MAINSTACK(); end end instruction push-register (?R) code jitterlisp_object k = JITTER_ARG0; JITTER_PUSH_MAINSTACK(k); end end instruction push-literal (?n jitterlisp_print) code jitterlisp_object k = JITTER_ARGN0; JITTER_PUSH_MAINSTACK(k); end end instruction push-global (?n jitterlisp_print, ?f) code jitterlisp_object tagged_symbol = JITTER_ARGN0; struct jitterlisp_symbol * symbol = JITTERLISP_SYMBOL_DECODE(tagged_symbol); #ifndef JITTERLISP_UNSAFE JITTER_BRANCH_FAST_IF_EQUAL (symbol->global_value, JITTERLISP_UNDEFINED, JITTER_ARGF1); #endif // #ifndef JITTERLISP_UNSAFE // JITTER_PUSH_MAINSTACK(symbol->global_value); JITTER_PUSH_UNSPECIFIED_MAINSTACK(); JITTER_TOP_MAINSTACK() = symbol->global_value; end end # FIXME: use these, in rewrites. instruction push-nil () code JITTER_PUSH_MAINSTACK(JITTERLISP_EMPTY_LIST); end end instruction push-zero () code JITTER_PUSH_MAINSTACK(JITTERLISP_FIXNUM_ENCODE(0)); end end instruction push-one () code JITTER_PUSH_MAINSTACK(JITTERLISP_FIXNUM_ENCODE(1)); end end instruction push-false () code JITTER_PUSH_MAINSTACK(JITTERLISP_FALSE); end end instruction push-nothing () code JITTER_PUSH_MAINSTACK(JITTERLISP_NOTHING); end end instruction pop-to-register (!R) code JITTER_ARG0 = JITTER_TOP_MAINSTACK(); JITTER_DROP_MAINSTACK(); end end instruction pop-to-global (?n jitterlisp_print, ?f) code jitterlisp_object tagged_symbol = JITTER_ARGN0; struct jitterlisp_symbol *symbol = JITTERLISP_SYMBOL_DECODE(tagged_symbol); #ifndef JITTERLISP_UNSAFE JITTER_BRANCH_FAST_IF_NONZERO (symbol->global_constant, JITTER_ARGF1); #endif // #ifndef JITTERLISP_UNSAFE symbol->global_value = JITTER_TOP_MAINSTACK(); JITTER_DROP_MAINSTACK(); end end instruction pop-to-global-defined (?n jitterlisp_print, ?f) code jitterlisp_object tagged_symbol = JITTER_ARGN0; struct jitterlisp_symbol *symbol = JITTERLISP_SYMBOL_DECODE(tagged_symbol); #ifndef JITTERLISP_UNSAFE JITTER_BRANCH_FAST_IF_NONZERO (symbol->global_constant, JITTER_ARGF1); JITTER_BRANCH_FAST_IF_EQUAL (symbol->global_value, JITTERLISP_UNDEFINED, JITTER_ARGF1); #endif // #ifndef JITTERLISP_UNSAFE symbol->global_value = JITTER_TOP_MAINSTACK(); JITTER_DROP_MAINSTACK(); end end # The first argument must not be zero. # Rationale: when I switch to JITTER_AT_DEPTH_UNSAFE_MAINSTACK this will make the # unspecialized case faster by avoiding a conditional. instruction at-depth-to-register (?n 1 2 3 4 5 6 7 8 9 10, !R) code // FIXME: replace with JITTER_AT_DEPTH_UNSAFE_MAINSTACK after I implement // unsafe at-depth operations. JITTER_ARG1 = JITTER_AT_DEPTH_MAINSTACK(JITTER_ARGN0); end end instruction copy-to-register (!R) code JITTER_ARG0 = JITTER_TOP_MAINSTACK(); end end instruction copy-from-register (?R) code JITTER_TOP_MAINSTACK() = JITTER_ARG0; end end instruction copy-from-literal (?n jitterlisp_print) code JITTER_TOP_MAINSTACK() = JITTER_ARGN0; end end instruction literal-to-register (?n jitterlisp_print, !R) code JITTER_ARG1 = JITTER_ARGN0; end end instruction register-to-register (?R, !R) code JITTER_ARG1 = JITTER_ARG0; end end # Do a check on the top element, fast-branching to the given error-handling # routine in case of mismatch, and do nothing otherwise. # Do *not* pop an operand from the stack. # FIXME: factor into a common comment for checking instructions. instruction check-closure (?f) code # ifndef JITTERLISP_UNSAFE jitterlisp_object top = JITTER_TOP_MAINSTACK(); if (! JITTERLISP_IS_CLOSURE(top)) JITTER_BRANCH_FAST(JITTER_ARGF0); # endif // #ifndef JITTERLISP_UNSAFE end end # Fast-branch to the given error label if the given argument, a tagged symbol, # is not globally bound. instruction check-global-defined (?n jitterlisp_print, ?f) code #ifndef JITTERLISP_UNSAFE jitterlisp_object tagged_symbol = JITTER_ARGN0; struct jitterlisp_symbol * symbol = JITTERLISP_SYMBOL_DECODE(tagged_symbol); JITTER_BRANCH_FAST_IF_EQUAL (symbol->global_value, JITTERLISP_UNDEFINED, JITTER_ARGF1); #endif // #ifndef JITTERLISP_UNSAFE end end # This versions is the one failing under no-threading. I want to keep it for tests # even if it's not the best implementation. # # The fixnum argument is untagged. instruction check-in-arity--alt (?n 0 1 2 3 4 5 6 7 8 9 10, ?f) code // FIXME: this loops forever with no-threading (not with the other dispatching models // including minimal threading) when the callee is compiled. A Jitter bug. /* Here we can assume that the top object is a closure, without checking: we either already performed a type check, or the compiler decided it wasn't necessary. */ jitterlisp_object top = JITTER_TOP_MAINSTACK(); struct jitterlisp_closure *c = JITTERLISP_CLOSURE_DECODE(top); /* FIXME: this is optimizable. I should store the in-arity as a field, independently from the closure compiledness. [Done] */ if (JITTERLISP_IS_COMPILED_CLOSURE(top)) { if (c->in_arity != JITTER_ARGN0) JITTER_BRANCH_FAST(JITTER_ARGF1); } else { /* The closure is interpreted. Compute its in-arity. We can assume that the formal list is well-formal, which is to say actually a proper list of distinct symbols. */ struct jitterlisp_interpreted_closure *ic = & c->interpreted; jitterlisp_object rest = ic->formals; jitter_uint in_arity = 0; while (! JITTERLISP_IS_EMPTY_LIST(rest)) { in_arity ++; rest = JITTERLISP_EXP_C_A_CDR(rest); } if (in_arity != JITTER_ARGN0) JITTER_BRANCH_FAST(JITTER_ARGF1); } end end instruction check-in-arity (?n 0 1 2 3 4 5 6 7 8 9 10, ?f) code # ifndef JITTERLISP_UNSAFE /* Here we can assume that the top object is a closure, without checking: we either already performed a type check, or the compiler decided it wasn't necessary. */ jitterlisp_object top = JITTER_TOP_MAINSTACK(); struct jitterlisp_closure *c = JITTERLISP_CLOSURE_DECODE(top); JITTER_BRANCH_FAST_IF_NOTEQUAL(c->in_arity, JITTER_ARGN0, JITTER_ARGF1); # endif // #ifndef JITTERLISP_UNSAFE end end instruction branch (?f) code JITTER_BRANCH_FAST(JITTER_ARGF0); end end instruction branch-if-false (?f) code jitterlisp_object top = JITTER_TOP_MAINSTACK(); JITTER_DROP_MAINSTACK(); JITTER_BRANCH_FAST_IF_EQUAL(top, JITTERLISP_FALSE, JITTER_ARGF0); end end instruction branch-if-true (?f) code jitterlisp_object top = JITTER_TOP_MAINSTACK(); JITTER_DROP_MAINSTACK(); JITTER_BRANCH_FAST_IF_NOTEQUAL(top, JITTERLISP_FALSE, JITTER_ARGF0); end end instruction branch-if-null (?f) code jitterlisp_object top = JITTER_TOP_MAINSTACK(); JITTER_DROP_MAINSTACK(); JITTER_BRANCH_FAST_IF_EQUAL(top, JITTERLISP_EMPTY_LIST, JITTER_ARGF0); end end instruction branch-if-not-null (?f) code jitterlisp_object top = JITTER_TOP_MAINSTACK(); JITTER_DROP_MAINSTACK(); JITTER_BRANCH_FAST_IF_NOTEQUAL(top, JITTERLISP_EMPTY_LIST, JITTER_ARGF0); end end instruction canonicalize-boolean () code JITTERLISP_BOOLEAN_CANONICALIZE_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end # The first argument is a primitive function pointer. # The second is its in-arity. # instruction primitive (?n, ?n 0 1 2 3 4, ?l) # non-relocatable instruction primitive (?n, ?n 0 1 2 3 4, ?f) code /* The error-handling label is not actually used in this case: the primitive function, written in C, has its own handling. It's harmless to have it anyway, as it makes the C code generator more uniform. */ jitterlisp_primitive_function p = (jitterlisp_primitive_function) JITTER_ARGP0; const jitter_uint in_arity = JITTER_ARGN1; JITTER_PUSH_UNSPECIFIED_MAINSTACK(); //asm volatile ("nop"); jitterlisp_object *first_arg // FIXME: add a stack operator to compute this address. = (& JITTER_UNDER_TOP_MAINSTACK()) - in_arity + 1; /* This workaround is needed for Alpha. I have macros to make this nicer in ~luca/repos/jitter/gcc-call-function-macro.c , still to be integrated into Jitter. */ /* void *saved_gp; asm volatile ("stq $gp, %[saved_gp]" : [saved_gp] "=o" (saved_gp), "+X" (p)); */ jitterlisp_object res = p (first_arg); /* Second part of the Alpha workaround. */ /* asm volatile ("ldq $gp, %[saved_gp]" : "+X" (res) : [saved_gp] "o" (saved_gp)); */ jitter_uint i; // Remove as many elements as the primitive in-arity, but not more: // the top unspecified value we pushed will be replaced with the result. for (i = 0; i < in_arity; i ++) JITTER_NIP_MAINSTACK(); //asm ("unop" : "+g" (jitter_state_runtime)); JITTER_TOP_MAINSTACK() = res; end end instruction primitive-car (?f) code JITTERLISPVM_CHECK_TYPES_1(CONS, JITTER_ARGF0); JITTERLISP_CAR_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-cdr (?f) code JITTERLISPVM_CHECK_TYPES_1(CONS, JITTER_ARGF0); JITTERLISP_CDR_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction heap-allocate (?n 4 8 12 16 24 32 36 48 52 64) code #ifdef JITTER_GC_STUB /* FIXME: I might want to merge this, along with the body of other instructions, into a macro. This may still be factored in a different way, but the idea looks correct and promising. */ /* Bump the allocation pointer, unconditionally. Another instruction will check if it went out of the nursery. */ const size_t allocation_size = JITTER_ARGN0; JITTER_STATE_RUNTIME_FIELD (allocation_next) += allocation_size; #else /* Nothing to do. With the ordinary non-stub heap all the actual memory allocation happens in primitive-cons-special. */ #endif //#ifdef JITTER_GC_STUB end end instruction gc-if-needed (?f) code #ifdef JITTER_GC_STUB /* FIXME: I might want to merge this, along with the body of other instructions, into a macro. This may still be factored in a different way, but the idea looks correct and promising. */ /* Branch to the slow path of the allocation, which includes a minor GC, in case the allocation pointer is now out of the nursery. Do nothing otherwise. Rationale: separating branches from instructions having effect on the VM state prevents defective instructions; even if I have reason to believe that this particular state effect (unconditionally incrementing a runtime field) is not problematic with respect to defects, I will need to seriously stress the system after defect replacement is really implemented. */ JITTER_BRANCH_FAST_IF_NOTLESS_UNSIGNED (JITTER_STATE_RUNTIME_FIELD (allocation_next), JITTER_STATE_RUNTIME_FIELD (allocation_limit), JITTER_ARGF0); #else /* Nothing to do. With the ordinary non-stub heap all the actual memory allocation happens in primitive-cons-special. */ #endif //#ifdef JITTER_GC_STUB end end # This doesn't nip, in the hope that the separate nip instruction generated # right after this will be rewritten. instruction primitive-cons-special () code #ifdef JITTER_GC_STUB /* This is a preliminary version of the allocation fast path, using a still non-existing Jitter garbage collector. Of course most of this should be factored into a macro. */ const size_t allocation_size = JITTER_BYTES_PER_WORD * 2; const size_t header_size = 0; jitterlisp_object tagged_cons = JITTERLISP_CONS_ENCODE (JITTER_STATE_RUNTIME_FIELD (allocation_next) - allocation_size + header_size); JITTERLISP_CONS_DECODE (tagged_cons)->car = JITTER_UNDER_TOP_MAINSTACK(); JITTERLISP_CONS_DECODE (tagged_cons)->cdr = JITTER_TOP_MAINSTACK(); JITTER_TOP_MAINSTACK() = tagged_cons; #else // ! JITTER_GC_STUB JITTERLISP_CONS_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); #endif //#ifdef JITTER_GC_STUB end end # The two VM instructions for the set-car! and set-cdr! operations do not # consume any operand from the stack, and so are followed by other VM # instructions to adjust the stack and set the TOS in compiled code. Such # separate instructions can often be rewritten away. instruction primitive-set-carb-special (?f) code JITTERLISPVM_CHECK_TYPES_2(CONS, ANYTHING, JITTER_ARGF0); jitterlisp_object useless __attribute__ ((unused)); JITTERLISP_SET_CARB_(useless, JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); /* Leave the two operands on the stack. */ end end instruction primitive-set-cdrb-special (?f) code JITTERLISPVM_CHECK_TYPES_2(CONS, ANYTHING, JITTER_ARGF0); jitterlisp_object useless __attribute__ ((unused)); JITTERLISP_SET_CDRB_(useless, JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); /* Leave the two operands on the stack. */ end end instruction primitive-box () code JITTERLISP_BOX_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-box-get (?f) code JITTERLISPVM_CHECK_TYPES_1(BOX, JITTER_ARGF0); JITTERLISP_BOX_GET_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end # See the comment above for set-car! and set-cdr! . The same performance # considerations hold for box-set! . instruction primitive-box-setb-special (?f) code JITTERLISPVM_CHECK_TYPES_2(BOX, ANYTHING, JITTER_ARGF0); jitterlisp_object useless __attribute__ ((unused)); JITTERLISP_BOX_SETB_(useless, JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); /* Leave the two operands on the stack. */ end end instruction primitive-eqp () code JITTERLISP_EQP_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-not-eqp () code JITTERLISP_NOT_EQP_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-fixnum-eqp (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_EQP_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-fixnum-not-eqp (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_NOT_EQP_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-zerop (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_ZEROP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-non-zerop (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_NON_ZEROP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-positivep (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_POSITIVEP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-non-positivep (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_NON_POSITIVEP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-negativep (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_NEGATIVEP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-non-negativep (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_NON_NEGATIVEP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-lessp (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_LESSP_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-greaterp (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_GREATERP_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-not-lessp (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_NOTLESSP_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-not-greaterp (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_NOTGREATERP_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-one-plus (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_1PLUS_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); end end instruction primitive-one-minus (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_1MINUS_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); end end instruction primitive-two-times (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_2TIMES_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); end end instruction primitive-two-divided (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_2DIVIDED_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-two-quotient (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_2QUOTIENT_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-two-remainder (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_2REMAINDER_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-negate (?f) code JITTERLISPVM_CHECK_TYPES_1(FIXNUM, JITTER_ARGF0); JITTERLISP_NEGATE_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); end end instruction primitive-primordial-plus (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_PLUS_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); JITTER_NIP_MAINSTACK(); end end instruction primitive-primordial-minus (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_MINUS_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); JITTER_NIP_MAINSTACK(); end end instruction primitive-primordial-times (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_TIMES_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); JITTER_NIP_MAINSTACK(); end end instruction primitive-primordial-divided (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_DIVIDED_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); JITTER_NIP_MAINSTACK(); end end instruction primitive-quotient (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_QUOTIENT_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); JITTER_NIP_MAINSTACK(); end end instruction primitive-remainder (?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF0); JITTERLISP_REMAINDER_OR_OVERFLOW_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK(), JITTER_ARGF0); JITTER_NIP_MAINSTACK(); end end instruction primitive-primordial-divided-unsafe (?f) code /* The second argument has already been validated if we are using this. */ JITTERLISPVM_CHECK_TYPES_2(FIXNUM, ANYTHING, JITTER_ARGF0); JITTERLISP_DIVIDED_UNSAFE_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-quotient-unsafe (?f) code /* The second argument has already been validated if we are using this. */ JITTERLISPVM_CHECK_TYPES_2(FIXNUM, ANYTHING, JITTER_ARGF0); JITTERLISP_QUOTIENT_UNSAFE_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-remainder-unsafe (?f) code /* The second argument has already been validated if we are using this. */ JITTERLISPVM_CHECK_TYPES_2(FIXNUM, ANYTHING, JITTER_ARGF0); JITTERLISP_REMAINDER_UNSAFE_(JITTER_TOP_MAINSTACK(), JITTER_UNDER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); JITTER_NIP_MAINSTACK(); end end instruction primitive-nullp () code JITTERLISP_NULLP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-non-nullp () code JITTERLISP_NON_NULLP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-nothingp () code JITTERLISP_NOTHINGP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-fixnump () code JITTERLISP_FIXNUMP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-characterp () code JITTERLISP_CHARACTERP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-uniquep () code JITTERLISP_UNIQUEP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-consp () code JITTERLISP_CONSP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-non-consp () code JITTERLISP_NON_CONSP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-symbolp () code JITTERLISP_SYMBOLP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-non-symbolp () code JITTERLISP_NON_SYMBOLP_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-not () code JITTERLISP_NOT_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end instruction primitive-boolean-canonicalize () code JITTERLISP_BOOLEAN_CANONICALIZE_(JITTER_TOP_MAINSTACK(), JITTER_TOP_MAINSTACK()); end end # Take a compiled closure (no type checking), its arguments already evaluated, # and the number of arguments (untagged) on the top of stack. Replace all the # stack operands with the call result. # Rationale: this instruction has been designed to be the first and only # instruction executed in the driver VM routine, called from the AST interpreter # in C. # The same driver routine can be generated once at initialization, and then # reused to launch procedures as needed: this is why the instruction takes # the closure in-arity from the stack instead of from an instruction argument. instruction call-from-c () caller code /* Pop a number (untagged) off the stack, so that I know at which depth to find the closure. The arity check must have been performed already on the interpreter side. */ const jitter_uint in_arity_plus_one = JITTER_TOP_MAINSTACK(); /* What follows is similar to the call instruction in the case of a compiled callee. Shall I factor the common code with a macro? */ /* Decode the closure. */ //#define STRESS #ifndef STRESS // FIXME: This is correct, and faster ... jitterlisp_object callee = JITTER_AT_NONZERO_DEPTH_MAINSTACK(in_arity_plus_one); JITTER_DROP_MAINSTACK(); #else // FIXME ...But this stresses the code better (mips gcc 8, x86_64 gcc 7). JITTER_DROP_MAINSTACK(); jitterlisp_object callee = JITTER_AT_DEPTH_MAINSTACK(in_arity_plus_one - 1); #endif // #ifndef STRESS struct jitterlisp_closure *c = JITTERLISP_CLOSURE_DECODE(callee); struct jitterlisp_compiled_closure *cc = & c->compiled; /* Make place for the return address in the return stack. The actual value will be written by the callee. */ JITTER_PUSH_UNSPECIFIED_RETURNSTACK(); /* Branch-and-link. This follows the ordinary calling convention, with the main stack containing the closure and its actuals, known to be in the correct number. The next VM instruction will be a prolog saving the link; then execution will fall thru into the compiled closure body. */ JITTER_BRANCH_AND_LINK(cc->first_program_point); end end # This is the generic code working for both compiled and interpreted closures. # FIXME: factor with the other call instructions. instruction call (?n 0 1 2 3 4 5 6 7 8 9 10) caller code const jitter_uint in_arity = JITTER_ARGN0; jitterlisp_object callee = JITTER_AT_DEPTH_MAINSTACK(in_arity); struct jitterlisp_closure *c = JITTERLISP_CLOSURE_DECODE(callee); if (c->kind == jitterlisp_closure_type_compiled) { /* Make place for the return address in the return stack. The actual value will be written by the callee. */ JITTER_PUSH_UNSPECIFIED_RETURNSTACK(); /* Branch-and-link to the native code, whose first instruction will be a prolog. */ struct jitterlisp_compiled_closure *cc = & c->compiled; JITTER_BRANCH_AND_LINK(cc->first_program_point); } else { const struct jitterlisp_interpreted_closure *ic = & c->interpreted; /* Push an element on the main stack. This way we can ensure that every actual is accessible thru memory in a contiguous array, even if the main stack is TOS-optimized. Compute the address (on the main stack backing) where the actuals begin. Notice that the interpreted function we are calling is allowed to call into compiled code in its turn. This is not a problem, as we are not moving stack pointers down until the call ends: the unused part of the main and return stacks is available to the callee. */ JITTER_PUSH_UNSPECIFIED_MAINSTACK(); jitterlisp_object *actuals // FIXME: add a stack operator to compute this address. = (& JITTER_UNDER_TOP_MAINSTACK()) - in_arity + 1; /* Call the interpreter. */ jitterlisp_object interpretation_result = jitterlisp_call_interpreted (ic, actuals, in_arity); /* Remove as many elements as the in-arity plus one (the callee) from the stack, without affecting the top. Unless JITTER_ARGN0 is residual this whole loops compiles to one instruction when the main stack is TOS-optimized, and doesn't touch memory. */ int i; for (i = 0; i < in_arity + 1; i ++) JITTER_NIP_MAINSTACK(); /* Now the stack is one element higher than it was before the call, containing the unspecified element at the top. Replace it with the result. Again this doesn't touch memory. */ JITTER_TOP_MAINSTACK() = interpretation_result; } end end # FIXME: factor with the other call instructions. instruction call-compiled (?n 0 1 2 3 4 5 6 7 8 9 10) caller code const jitter_uint in_arity = JITTER_ARGN0; jitterlisp_object callee = JITTER_AT_DEPTH_MAINSTACK(in_arity); struct jitterlisp_closure *c = JITTERLISP_CLOSURE_DECODE(callee); /* Here we can assume that * c is a compiled closure without checking. */ struct jitterlisp_compiled_closure *cc = & c->compiled; /* Make place for the return address in the return stack. The actual value will be written by the callee. */ JITTER_PUSH_UNSPECIFIED_RETURNSTACK(); // JITTER_TOP_RETURNSTACK() = 0x4242aaaa; // FIXME: just a test. Remove. /* Branch-and-link to the native code, whose first instruction will be a prolog. */ JITTER_BRANCH_AND_LINK(cc->first_program_point); end end # FIXME: factor with the other call instructions. instruction tail-call (?n 0 1 2 3 4 5 6 7 8 9 10) code const jitter_uint in_arity = JITTER_ARGN0; jitterlisp_object callee = JITTER_AT_DEPTH_MAINSTACK(in_arity); struct jitterlisp_closure *c = JITTERLISP_CLOSURE_DECODE(callee); if (c->kind == jitterlisp_closure_type_compiled) { /* Extract the current return address from the return stack, to be reused. Differently from non-tail calls, don't push anything on the return stack: the callee will overwrite the top with its current value, which is what we want. */ jitterlispvm_program_point current_link = (jitterlispvm_program_point) JITTER_TOP_RETURNSTACK(); /* Branch-and-relink to the native code, whose first instruction will be a prolog. */ struct jitterlisp_compiled_closure *cc = & c->compiled; JITTER_BRANCH_AND_LINK_WITH(cc->first_program_point, current_link); } else { /* Unfortunately I cannot really tail-call from compiled code to interpreted code. Instead I will call the interpreter as a C function and then return. */ const struct jitterlisp_interpreted_closure *ic = & c->interpreted; /* Push an element on the main stack. This way we can ensure that every actual is accessible thru memory in a contiguous array, even if the main stack is TOS-optimized. Compute the address (on the main stack backing) where the actuals begin. Notice that the interpreted function we are calling is allowed to call into compiled code in its turn. This is not a problem, as we are not moving stack pointers down until the call ends: the unused part of the main and return stacks is available to the callee. */ JITTER_PUSH_UNSPECIFIED_MAINSTACK(); jitterlisp_object *actuals // FIXME: add a stack operator to compute this address. = (& JITTER_UNDER_TOP_MAINSTACK()) - in_arity + 1; /* Call the interpreter. */ jitterlisp_object interpretation_result = jitterlisp_call_interpreted (ic, actuals, in_arity); /* Remove as many elements as the in-arity plus one (the callee) from the stack, without affecting the top. Unless JITTER_ARGN0 is residual this whole loops compiles to one instruction when the main stack is TOS-optimized, and doesn't touch memory. */ int i; for (i = 0; i < in_arity + 1; i ++) JITTER_NIP_MAINSTACK(); /* Now the stack is one element higher than it was before the call, containing the unspecified element at the top. Replace it with the result. Again this doesn't touch memory. */ JITTER_TOP_MAINSTACK() = interpretation_result; /* Return to our original caller. */ jitter_uint return_address = JITTER_TOP_RETURNSTACK(); JITTER_DROP_RETURNSTACK(); JITTER_RETURN(return_address); } end end # FIXME: factor with the other call instructions. instruction tail-call-compiled (?n 0 1 2 3 4 5 6 7 8 9 10) #caller code const jitter_uint in_arity = JITTER_ARGN0; jitterlisp_object callee = JITTER_AT_DEPTH_MAINSTACK(in_arity); struct jitterlisp_closure *c = JITTERLISP_CLOSURE_DECODE(callee); /* Here we can assume that * c is a compiled closure without checking. */ struct jitterlisp_compiled_closure *cc = & c->compiled; /* Take the current return address from the return stack: I want to keep returning there. Differently from the case of non-tail calls I don't push anything on the return stack: the callee prolog will overwrite the current top with a copy of itself, which is fine. */ jitterlispvm_program_point current_link = (jitterlispvm_program_point) JITTER_TOP_RETURNSTACK(); /* Branch-and-relink to the native code, whose first instruction will be a prolog. */ JITTER_BRANCH_AND_LINK_WITH(cc->first_program_point, current_link); end end instruction procedure-prolog () callee code /* Fill the return stack slot with the return address. The return stack has already been pushed (with an unspecified value on the top) by the caller. */ JITTER_TOP_RETURNSTACK() = (jitterlisp_object) JITTER_LINK; //dump_stacks (0, the_jitter_state_runtime_pointer); end end instruction return () code //dump_stacks (1, the_jitter_state_runtime_pointer); jitter_uint return_address = JITTER_TOP_RETURNSTACK(); JITTER_DROP_RETURNSTACK(); //dump_stacks (2, the_jitter_state_runtime_pointer); JITTER_RETURN(return_address); //dump_stacks (1, the_jitter_state_runtime_pointer); end end instruction save-register (?R) code JITTER_PUSH_RETURNSTACK(JITTER_ARG0); end end instruction restore-register (!R) code JITTER_ARG0 = JITTER_TOP_RETURNSTACK(); JITTER_DROP_RETURNSTACK(); end end instruction fail () non-relocatable cold code jitterlisp_fail_from_vm (); end end ## Optimization rewrites. ################################################################# rule pop-to-register-push-register rewrite pop-to-register $a; push-register $b into copy-to-register $a; copy-from-register $b end rule pop-to-register-push-literal rewrite pop-to-register $a; push-literal $b into copy-to-register $a; copy-from-literal $b end rule push-literal-pop-to-register rewrite push-literal $a; pop-to-register $b into literal-to-register $a, $b end rule drop-push-register rewrite drop; push-register $a into copy-from-register $a end rule drop-push-literal rewrite drop; push-literal $a into copy-from-literal $a end rule drop-push-unspecified rewrite drop; push-unspecified into # Nothing. end rule push-register-pop-to-register rewrite push-register $a; pop-to-register $b into register-to-register $a, $b end rule copy-from-register-pop-to-register rewrite copy-from-register $a; pop-to-register $b into register-to-register $a, $b; drop end rule copy-from-literal-pop-to-register rewrite copy-from-literal $a; pop-to-register $b into literal-to-register $a, $b; drop end rule push-register-push-register rewrite push-register $a; push-register $a into push-register $a; dup end rule copy-from-register-push-register rewrite copy-from-register $a; push-register $a into copy-from-register $a; dup end rule copy-to-register-and-push-the-same-register rewrite copy-to-register $a; push-register $a into copy-to-register $a; dup end rule copy-to-and-from-the-same-register rewrite copy-to-register $a; copy-from-register $a into copy-to-register $a end rule push-the-same-literal-twice rewrite push-literal $a; push-literal $a into push-literal $a; dup end rule copy-from-literal-and-push-the-same-literal rewrite copy-from-literal $a; push-literal $a into copy-from-literal $a; dup end # Remove the first instruction in a two-instruction sequence made of copy-from # instructions. rule copy-from-literal-then-from-another-literal rewrite copy-from-literal $a; copy-from-literal $b into copy-from-literal $b end rule copy-from-literal-then-from-a-register rewrite copy-from-literal $a; copy-from-register $b into copy-from-register $b end rule copy-from-a-register-then-from-literal rewrite copy-from-register $a; copy-from-literal $b into copy-from-literal $b end rule copy-from-a-register-then-from-another-register rewrite copy-from-register $a; copy-from-register $b into copy-from-register $b end # A copy-from instruction is useless when immediately followed by a drop. rule useless-copy-from-literal-elimination rewrite copy-from-literal $a; drop into drop end rule useless-copy-from-register-elimination rewrite copy-from-register $a; drop into drop end rule push-register-drop rewrite push-register $a; drop into # Nothing. end rule push-literal-drop rewrite push-literal $a; drop into # Nothing. end rule pop-to-register-copy-from-register rewrite pop-to-register $a; copy-from-register $a into copy-to-register $a; nip end rule pop-to-register-drop rewrite pop-to-register $a; drop into nip; pop-to-register $a end rule pop-to-register-nip rewrite pop-to-register $a; nip into copy-to-register $a; drop-nip end rule drop-nip rewrite drop; nip into drop-nip end rule nip-drop rewrite nip; drop into nip-drop end rule drop-drop rewrite drop; drop into nip-drop end rule nip-drop-drop rewrite nip-drop; drop into nip-two-drop end rule nip-two-drop-drop rewrite nip-two-drop; drop into nip-three-drop end rule nip-three-drop-drop rewrite nip-three-drop; drop into nip-four-drop end rule nip-four-drop-drop rewrite nip-four-drop; drop into nip-five-drop end rule nip-five-drop-drop rewrite nip-five-drop; drop into nip-six-drop end # Combine consecutive nip instructions into single instructions. This # implementation will become nicer and more general when Jitter rewrite rules # become more expressive. rule nip-nip rewrite nip; nip into nip-two end rule nip-two-nip rewrite nip-two; nip into nip-three end rule nip-three-nip rewrite nip-three; nip into nip-four end rule nip-four-nip rewrite nip-four; nip into nip-five end rule nip-five-nip rewrite nip-five; nip into nip-six end # Move nip instructions before copy-from instructions. This will make it # easier to rewrite them into a single nip-multiple instruction. rule nip-before-copy-from-register rewrite copy-from-register $a; nip into nip; copy-from-register $a end rule nip-before-copy-from-literal rewrite copy-from-literal $a; nip into nip; copy-from-literal $a end # Having copy-to-register at the end might make it possible to combine # with some other instruction; nip is not easy to combine, except with # other nips.. rule copy-to-register-nip rewrite copy-to-register $a; nip into nip; copy-to-register $a end rule pop-to-register-return rewrite pop-to-register $a; return into drop; return end rule copy-to-register-return rewrite copy-to-register $a; return into return end # The AST rewriter takes care of removing most "not; branch-if-*" sequences, # including every use of "not" as a condition and every use of # "boolean-canonicalize" as a guard or condition; however # "not; branch-if-true" sequnces remain as while guards, where the branch is # always on a true condiion (I compile while loops as do..while, with the # conditional branch at the end). It's easier to optimize this pattern here # than to add a special case to the compiler. rule not-branch-if-true rewrite primitive-not; branch-if-true $b into branch-if-false $b end rule nullp-branch-if-true rewrite primitive-nullp; branch-if-true $a into branch-if-null $a end rule nullp-branch-if-false rewrite primitive-nullp; branch-if-false $a into branch-if-not-null $a end rule non-nullp-branch-if-true rewrite primitive-non-nullp; branch-if-true $a into branch-if-not-null $a end rule non-nullp-branch-if-false rewrite primitive-non-nullp; branch-if-false $a into branch-if-null $a end # # Some save/restore pairs are easy to eliminate. # # FIXME: no, this never fires because of the implicit label after the call, # # which makes the sequence non-rewritable. Do I really want this behavior? # # I suspect not. [FIXME: the behavior is no longer there.] # rule rewrite # save-register $a; call $b; restore-register $a; primitive-primordial-plus $c; return # into # call $b; primitive-primordial-plus $c; return # end # Some useless restore/save pairs come from compiling nested non-tail procedure calls. # Notice that this fires multiple times with multiple nested pairs such as # restore-register %r1; restore-register %r0; save-register %r0; save-register %r1 # , until every pair has been eliminated. rule restore-register-then-save-the-same-register rewrite restore-register $a; save-register $a into # Nothing. end # # FIXME: this is a temporary kludge, to be used before I actually implement # # tail calls. # rule remove-tail-call--kludge rewrite # tail-call $a # into # call $a; return # end # rule remove-tail-call-compiled--kludge rewrite # tail-call-compiled $a # into # call-compiled $a; return # end ## Scratch. ################################################################# instruction branch-if-register-non-zero (?R, ?f, ?f) code #if ! defined (JITTERLISP_UNSAFE) JITTERLISP_BRANCH_FAST_UNLESS_FIXNUM (JITTER_ARG0, JITTER_ARGF2); #endif JITTER_BRANCH_FAST_IF_NOTEQUAL(JITTER_ARG0, JITTERLISP_FIXNUM_ENCODE(0), JITTER_ARGF1); end end rule scratch rewrite push-register $a; primitive-non-zerop $b; branch-if-true $c into branch-if-register-non-zero $a, $c, $b end instruction branch-if-not-less (?f, ?f) code JITTERLISPVM_CHECK_TYPES_2(FIXNUM, FIXNUM, JITTER_ARGF1); jitterlisp_object undertop = JITTER_UNDER_TOP_MAINSTACK(); jitterlisp_object top = JITTER_TOP_MAINSTACK(); JITTER_NIP_MAINSTACK(); JITTER_DROP_MAINSTACK(); JITTER_BRANCH_FAST_IF_NOTLESS_SIGNED(undertop, top, JITTER_ARGF0); end end # FIXME: write more rules like this one. # rule rewrite # primitive-lessp $a; branch-if-false $b # into # branch-if-not-less $b, $a # end rule nip-drop-push-literal rewrite nip-drop; push-literal $a into nip; copy-from-literal $a end rule nip-drop-push-register rewrite nip-drop; push-register $a into nip; copy-from-register $a end rule nip-two-drop-push-literal rewrite nip-two-drop; push-literal $a into nip-two; copy-from-literal $a end rule nip-two-drop-push-register rewrite nip-two-drop; push-register $a into nip-two; copy-from-register $a end rule nip-three-drop-push-literal rewrite nip-three-drop; push-literal $a into nip-three; copy-from-literal $a end rule nip-three-drop-push-register rewrite nip-three-drop; push-register $a into nip-three; copy-from-register $a end rule nip-four-drop-push-literal rewrite nip-four-drop; push-literal $a into nip-four; copy-from-literal $a end rule nip-four-drop-push-register rewrite nip-four-drop; push-register $a into nip-four; copy-from-register $a end rule nip-five-drop-push-literal rewrite nip-five-drop; push-literal $a into nip-five; copy-from-literal $a end rule nip-five-drop-push-register rewrite nip-five-drop; push-register $a into nip-five; copy-from-register $a end rule nip-six-drop-push-literal rewrite nip-six-drop; push-literal $a into nip-six; copy-from-literal $a end rule nip-six-drop-push-register rewrite nip-six-drop; push-register $a into nip-six; copy-from-register $a end # For these rule to fire the cons primitive use must be in a non-tail position, # used as a non-last operand of a primitive or procedure. # Testcases: alist-copy zip-reversed-iterative zip-reversed-tail-recursive-helper # zip-non-tail-recursive unzip-non-tail-recursive . instruction nip-push-literal (?n) code JITTER_UNDER_TOP_MAINSTACK() = JITTER_TOP_MAINSTACK(); JITTER_TOP_MAINSTACK() = JITTER_ARGN0; end end instruction nip-push-register (?R) code JITTER_UNDER_TOP_MAINSTACK() = JITTER_TOP_MAINSTACK(); JITTER_TOP_MAINSTACK() = JITTER_ARG0; end end rule rewrite nip; push-literal $a into nip-push-literal $a end rule rewrite nip; push-register $a into nip-push-register $a end # Test case: (lambda () (while #t (cons 1 2))) rule useless-cons-elimination rewrite heap-allocate $a; gc-if-needed $f; primitive-cons-special; nip-drop into # Don't cons at all and drop the two cons operands as well, which will # hopefully rewrite further. I don't generate just one nip-drop instruction # because the first rewritten drop might already rewrite along with its # previous instruction; and so may the second. drop; drop end