summaryrefslogtreecommitdiffstats
path: root/vm.c
Commit message (Collapse)AuthorAgeFilesLines
* vm: heap corruption bug.Kaz Kylheku2018-04-251-1/+1
| | | | | | | | | | | | * vm.c (vm_execute_toplevel): Fix data vector being assigned to the wrong display frame, leaving vm.dspl[1].vec uninitialized. Why is that a problem? Because the VM depends on these vectors when performing the vm_set operation: if a frame register is stored, and the frame has an associated vector, mut_obj is invoked on that vector. Now that there exists the load-time operator, the d regs (which live in dspl[1]) can be mutated. That causes mut_obj to be called with garbage. This was all discovered during testing on PPC64.
* vm: de-inline opcode dispatch.Kaz Kylheku2018-04-251-41/+41
| | | | | | | | | | | | | | | | | | | The vm_execute function is heavily inlined by gcc, and requires almost 500 bytes of stack space. The stack space really adds up when the vm re-enters itself recursively. Also, pointers to garbage can hide in areas of that bloated stack frame that are not being used by execution paths, adding to the spurious retention problem. * lib.h (NOINLINE): New preprocessor symbol. * vm.c (vm_prof, vm_frame, vm_sframe, vm_dframe, vm_end, vm_fin, vm_call, vm_apply, vm_gcall, vm_gapply, vm_movrs, vm_movsr, vm_movrr, vm_movrsi, vm_movsmi, vm_movrbi, vm_if, vm_ifq, vm_ifql, vm_swtch, vm_uwprot, vm_block, vm_no_block_err, vm_retsr, vm_retrs, vm_retrr, vm_abscsr, vm_catch, vm_handle, vm_getsym, vm_getbind, vm_setsym, vm_bindv, vm_close, vm_execute): Apply INLINE to functions.
* vm: null out variable arg list.Kaz Kylheku2018-04-251-1/+1
| | | | | * vm.c (vm_execute_closure): Null out the vargs local to prevent spurious retention.
* vm: destroy t-reg values used as call args.Kaz Kylheku2018-04-241-17/+25
| | | | | | | | | | Whe the compiler uses t-regs as function call arguments, they are t-regs that have no next use and can be nulled out. We do this to prevent false retention. * vm.c (vm_getz): New function. (vm_call, vm_apply, vm_gcall, vm_gapply): Use vm_getz for fetching arguments.
* New macro: load-time.Kaz Kylheku2018-04-231-6/+0
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This is similar to the ANSI CL load-time-value. * eval.c (load_time_s, load_time_lit_s): New symbol variables. (op_load_time_lit, me_load_time): New static functions. (eval_init): Intern load-time symbol and sys:load-time-lit. Register the sys:load-time-lit special operator and load-time macro. * share/txr/stdlib/asm.tl (assembler parse-args): We must now allow the d registers to be the targets of a mov instruction, because load-time depends on being able to mutate the data vector, in order to turn the result of a calculation into a de facto literal. * share/txr/stdlib/compiler.tl (compiler): New member, lt-frags. (compile-in-toplevel): New macro. (compiler alloc-dreg): New method. (compiler compile): Handle sys:load-time-lit special form via comp-load-time-lit method. (compiler comp-load-time-lit): New method. (usr:compile-toplevel): Prepend the load-time assembly code fragments to the compiled assembly code. * vm.c (vm_set, vm_sm_set): Do not reject an attempt to modify the static data, since load-time now generates mov instructions targetting the d registers. * txr.1: Document load-time.
* apply: remove remaining apply_intrinsic uses.Kaz Kylheku2018-04-181-1/+1
| | | | | | | | | * eval.c (apply_intrinsic): Function removed. (to_apf): Use applyv instead of apply_intrinsic. * eval.h (apply_intrinsic): Declaration removed. * vm.c (vm_gapply): Use applyv instead of apply_intrinsic.
* apply: eliminate wasteful consing.Kaz Kylheku2018-04-181-1/+1
| | | | | | | | | | | | | | | | | | | | Now (pprof (apply '+ 1 2 3 4 5 '(6 7))) shows zero bytes consed. Previously 176 (on 32 bit). This is the same whether the expression is compiled or interpreted. * eval.c (applyv): Rewritten to efficiently manipulate the args and call generic_funcall directly. The consing funcction apply_intrinsic_frob_args is only used when args contains a trailing list (args->list) and is only used on that trailing list, not the entire arg list. Also, changing the static function to external. * eval.h (applyv): Declared. * vm.c (vm_apply): Use applyv instead of wastefully converting the arguments to a consed list and going through apply_intrinsic.
* vm/asm: reconfiguration of display dimensions.Kaz Kylheku2018-04-171-4/+4
| | | | | | | | | | | | | | | | | | | | | | | | | | Increasing the maximum frame width from 256 to 1024 words; and reducing the max display depth from 256 to 64 frames. Small operands use a 4:6 split: the first 64 words of the first 16 levels can be accessed with a 10 bit operand. * share/txr/stdlib/asm.tl (parse-operand): Adjust to wider syntax for registers due to more digits. The v registers are 5 hex digits now: 2 digit level, and a 3 digit offset. (with-lev-idx): New macro. (operand-to-sym): Use with-lev-idx and rename variables to lv and ix. (small-op-p, enc-op-p, small-op-to-sym): Rewrite for 4:6 small operand. * share/txr/stdlib/vm-param.tl (%lev-size%, %lev-bits%, %max-lev%): Adjust values. (%sm-lev-size%, %max-sm-lev-idx%, sm-lev-bits%): New symbol macros. * vm.c (VM_LEV_BITS, VM_LEV_MASK, VM_SM_LEV_BITS, VM_SM_LEV_MASK): Values adjusted.
* vm: clear stack memory moved to heap.Kaz Kylheku2018-04-171-1/+3
| | | | | | | * vm.c (vm_make_closure): When a display frame is captured by a closure and moves into the heap object, the original should be cleared to all nil values, otherwise it can cause spurious retention of garbage.
* vm/asm/compiler: parametrize display parameters.Kaz Kylheku2018-04-161-13/+45
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | In this patch we replace some hard-coded constants related to the dimensions of the frame display, which will make it easier to experiment with changes to the configuration: to set up wider frames at the cost of max display depth. The encoding of small operands needs to be abstracted because games can be played here. A 10 bit small operand can be configured in various ways: for instance a 5:5 split would mean that the first 32 entries in the first 32 levels can be small operands. That's independent of the display configuration. * share/txr/stdlib/asm.tl: Load vm-param to get some fundamental constants. (small-op-p): New macro. (assembler parse-args, parse-compound-operand, parse-operand, operand-to-sym): Use constants. (enc-small-oip, small-op-to-sym): New macros. (op-frame asm): Use constants. Small range check problem fixed here: a frame level of 1 mustn't be allowed. (op-movrs, op-movsr, op-mov-pseudo, op-movsmi, op-movrbi, op-movi-pseudo, op-retsr, op-retrs, op-retrr, op-ret-pseudo, op-getv, op-setv): Deal with small operand via functions. (op-close): Use constants. Off-by-one problem: frame size of 256 must be allowed. * share/txr/stdlib/compiler.tl: Load vm-param to get constants. (compiler get-dreg, compiler get-funvec): Use constant for max frame size. (compiler new-env): Check for maximum levels being exceeded. (compiler comp-atom): Use constant for maximum immediate integer operand width. * share/txr/stdlib/vm-param.tl: New file. * vm.c (vm_insn_extra_dec_small): New macro. (VM_LEV_BITS, VM_LEV_MASK, VM_SM_LEV_BITS, VM_SM_LEV_MASK): New preprocessor constants. (vm_lev, vm_idx, vm_sm_lev, vm_sm_idx): New macros. (vm_get, vm_set): Use macros instead of literals. (vm_sm_get, vm_sm_set): New inline functions. (vm_movrs, vm_movsr, vm_movsmi, vm_retsr, vm_retrs, vm_abscsr, vm_get_binding, vm_bindv): Use vm_sm_get or vm_sm_set to access the display using the small operand. (vm_close): Use preprocessor symbols instead of hard-coded literals.
* vm: remove unused, bogus definitions.Kaz Kylheku2018-04-161-8/+0
| | | | * vm.c (vm_opcode, vm_oparg): Unused macros removed.
* vm: don't eagerly resolve functions.Kaz Kylheku2018-04-101-7/+0
| | | | | | | | | | | | | | | * vm.c (vm_make_desc): Do not walk ftab to resolve the function bindings at VM instantiation time. Just let it be done late in vm_ftab when a gcall or gapply instruction is executing. We don't gain anything by doing it early; there is no error checking or anything. This early resolution causes the autoload semantics of the standard library modules to change between interpreted and compiled. When a compiled module is loaded, it immediately triggers autloads of everything that it potentially references. This suddenly broke the build under a newer GNU Make which doesn't sort the files expanded by $(wildcard ...), causing the library to be compiled in a different order.
* vm: new accessors for closure objects.Kaz Kylheku2018-04-071-0/+19
| | | | | | | * vm.c (vm_closure_struct): New static function. (vm_closure_desc, vm_closure_entry): New functions. (vm_init): sys:vm-closure-desc and sys:vm-closure-entry intrinsics registered.
* vm: C++ port issue: wrong cast.Kaz Kylheku2018-04-061-1/+1
| | | | | * vm.c (vm_insn_opcode): Conversion between integer types requires convert rather than coerce.
* compiler: first cut compile-file implementation.Kaz Kylheku2018-04-031-0/+14
| | | | | | | | | | | | | | | | | | | | | | * lisplib.c (compiler_set_entries): Autoload on compile-file. * parser.c (parse_init): Expose get-parser, parser-errors and parser-eof intrinsics in system package. * share/txr/stdlib/compiler.tl (compiler): Wrap defstruct form in compile-only. What this means is that when we invoke comile-file on compiler.tl, the compiler will only compile this defstruct and not evaluate it. I.e. it will not try to redefine the structure. Redefining the core structure of the compiler while it is compiling itself wreaks havoc on the compilation. (%fille-suff-rx%, *emit*, *eval*): New variables. (open-compile-streams, list-from-vm-desc, usr:compile-file): New functions. * vm.c (vm_desc_nlevels, vm_desc_nregs): New static functions. (vm_init): Register new intrinsics vm-desc-nlevels and vn-desc-nregs in system package.
* vm: integrate with delimited continuations.Kaz Kylheku2018-03-291-0/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | It doesn't Just Work out of the box. Here is why. The struct vm state is declared somewhere on the stack, and then the vm executes various recursive functions. A block is established by vm_block(). Then if a continuation is captured up to that block, it will not include that part of the stack which holds the VM state. When the continuation is restarted, the struct vm * pointers inside the stack will be garbage. The solution: when a continuation is captured to a prompt that is set up by the VM block instruction (vm_block function), we put information into the block which says "don't just capture me plus some slack: please capture the stack all the way up to this specific address here". That address, of course, is just past the VM state, ensuring that the VM state is included in the snapshot. In short: a delimited continuation terminating in a prompt set up by the VM just include the entire VM context from the stack frame where the struct vm on down (i.e. up the stack) to the capture point. * unwind.c (uw_push_block): Initialize cont_bottom member to zero. Interpreted blocks leave this as zero. Blocks set up by the VM override it. (revive_cont): Critical: the range check must include the orig_end endpoint, because when the struct vm is captured into the continuation it is right at the end of the capture memory, and the prompt block's cont_bottom member points exactly to orig_end: one element past the struct vm. The cont_bottom member must be adjusted by delta so that continuations captured by the revived VM inside the continuation get the adjusted cont_bottom at their current stack location. (capture_cont): If the block is publishing a non-zer cont_bottom value, then take that value if it is a higher address than the lim (including the UW_CONT_FRAME_AFTER slack). * unwind.h (struct uw_block): New member, cont_bottom. By means of this, a block can indicate a higher address to which a continuation capture can extend. * vm.c (vm_block): Publish the address one byte past the virtual machine state as the stack bottom for continuations.
* vm/asm: new prof instruction.Kaz Kylheku2018-03-271-0/+16
| | | | | | | | | | * share/txr/stdlib/asm.tl (prof): New opcode. (op-prof): New opcode class. * vm.c (vm_prof_callback, vm_prof): New static functions. (vm_execute): Handle PROF opcode via vm_prof. * vmop.h: Regenerated.
* vm/asm: new swtch instruction.Kaz Kylheku2018-03-261-0/+21
| | | | | | | | | | | | | * share/txr/stdlib/asm.tl (backpatch-low16, backpatch-high16): New struct types. (%backpatch-low16%, %backpatch-high16%): New global variables. (swtch): New opcode. (op-swtch): New opcode class. * vm.c (vm_swtch): New static function. (vm_execute): Handle SWTCH opcode via vm_swtch. * vmop.h: Regenerated.
* vm: bugfix: faulty memcpy in closure.Kaz Kylheku2018-03-261-1/+1
| | | | | | * vm.c (vm_make_closure): Add missing scale factor to memcpy. This is the memcpy which relocates display frame contents from the stack to the frame.
* compiler/vm: implement sys:abscond-from special form.Kaz Kylheku2018-03-261-0/+12
| | | | | | | | | | | | | | | * share/txr/stdlib/asm.tl (abscsr): New instruction. (op-abscsr): New opcode class, derived from op-retsr. * share/txr/stdlib/compiler.tl: Handle sys:abscond-from via comp-return-from method. (compiler comp-return-from): Handle sys:abscond-from by switching to abscsr opcode instead of ret pseudo-op. * vm.c (vm_abscsr): New static function. (vm_execute): Dispatch ABSCSR opcode. * vmop.h: Regenerated.
* vm: change encoding of getv, setv and related.Kaz Kylheku2018-03-231-6/+6
| | | | | | | | | | | | | | | | | | | | | | | In most compiler uses of bindv, getv and setv, the operand which names the dynamic symbol is the data table: a d0-dff register. The source or destination register of the transfer could be anything. Therefore, the existing encoding is suboptimal: the symbol is being put into the full sized operand field of the instruction, and the source or destination register into the small, ten-bit extra field in the upper half. This breaks on cases like (set x y) where x is a deeply nested lexical variable and y is a dynamic variable. Let's reverse the encoding. * share/txr/stdlib/asm.tl (op-getv, op-setv): Reverse the operands. All derived opcodes follow this since they reuse the code via inheritance. * vm.c (vm_get_binding): Fetch the symbol from the small operand field rather than the main operand field. (vm_getsym, vm_getbind, vm_setsym, vm_bindv): Pull the destination or source from the main operand of the instruction rather than the small opreand.
* vm: initialize vd->funvec to nil.Kaz Kylheku2018-03-221-0/+1
| | | | | | | | * vm.c (vm_make_desc): Initialize vd->funvec to nil so that when the object is created, it doesn't have a garbage field. However, there is no risk here that the field will be traversed by the garbage collector, since immediately after allocating the objct, we initialize the fields.
* vm: bugfix: cannot access vec during gc.Kaz Kylheku2018-03-221-6/+8
| | | | | | | | | | | | | | Let's have a field in struct vm_desc that keeps track of the ftab size. The problem is when we do length_vec(vd->funvec) in vm_desc_mark, we are in the middle of garbage collection. The object's tag has a reachable bit which blows up the type check. * vm.c (struct vm_desc): New member, ftsz. (vm_make_desc): Store the length of the function table into ftsz. (vm_desc_mark): Use the stored length as the loop bound; do not access vd->funvec.
* vm: ret instructions: throw error if block not found.Kaz Kylheku2018-03-221-0/+14
| | | | | | * vm.c (vm_no_block_err): New static function. (vm_retsr, vm_retrs, vm_retrr): Call vm_no_block_err if uw_block_return returns.
* vm: allocate display in same object as closure.Kaz Kylheku2018-03-201-14/+9
| | | | | | | | | | * vm.c (struct vm_closure): Redeclare display member as array of 1: old-fashioned version of C struct hack. (vm_make_closure): Make the necessary adjustments to the allocation code. (vm_closure_destroy): Static function removed. (vm_closure_mark): Wire in cobj_destroy_free_op once again, since there is just one object to free.
* vm: support deferred resolution of ftab bindings.Kaz Kylheku2018-03-191-4/+20
| | | | | | | | | | | | | | | | This allows a virtual machine's funvec to refer to functions that are not yet defined. We resolve all the ones that are defined when the virtual machine description is constructed. The others are resolved at vm execution time when accessed. * vm.c (vm_make_desc): When filling ftab with resolved function bindings, check that lookup_fun has returned nil; in that case, initialize the binding location to zero, instead of blowing up calling cdr_l(nil). (vm_ftab): New static function. Implements the null check and late resolution. (vm_gcall, vm_gapply): Acces ftab through vm_ftab instead of open-coded expression.
* vm: bug: vm-desc created with incorrect display depth.Kaz Kylheku2018-03-191-0/+2
| | | | | | | | | | | | | | | | | | * share/txr/stdlib/compiler.tl (sys:env :postinit): The call to register the environment with the compiler must be outside of the unless form. Otherwise it never takes place, and so the compiler doesn't find the maximum number of environment levels, keeping the value at 2. The executing vm then accesses out of bounds memory when setting up display frames. (usr:compile-toplevel): Give the root environment the compiler. Not strictly necessary since we are constent in doing this elsewhere, so we are not relying on inheritance of the compiler from parent environment to child. * vm.c (vm_make_closure): assert added for the environment levels of the closure not exceeding the display depth given in the machine description. This was added during debugging and going off; I'm keeping it.
* vm: variadic arg closures bug 2/3.Kaz Kylheku2018-03-191-6/+5
| | | | | | | | | | | | | | * vm.c (vm_execute_closure): Remove bogus nargs local variable, and replace its uses by fixparam which is the correct one to use for getting the list of trailing arguments using args_get_rest and for the loop which extracts the fixed arguments. The "if (variadic)" statement near the end specifically depends on the variadic destination register not having been extracted yet from the instruction space, so the argument count used in the previousl loop cannot include that register. This bug was causing that statement to extract a zero, thus register t0, which then blew up in the vm execution as an attempt to modify t0.
* vm: function table for faster calls.Kaz Kylheku2018-03-181-3/+109
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The vm now supports gcall and gapply opcodes which index numerically (using an immediate integer field in the instruction word) into a table of pre-resolved global function bindings. * share/txr/stdlib/asm.tl (op-gcall, op-gapply): New opcodes. (disassemble-c-d): Take the function vector as an argument and dump it too. (usr:disassemble): Extract function vector from VM description and pass it to disassemble-c-d. * share/txr/stdlib/compiler.tl (usr:compile-toplevel): Pass empty function symbol vector to vm-make-desc; it's now a required argument. * vm.c (struct vm_desc): New members funvec and ftab. (struct vm_ftent): New struct type. (vm_make_desc): New argument, funvec. Store funvec in the descriptor. Allocate a table of vm_ftent structures equal in number of elements to the function table, and populate it with resolved bindings. (vm_desc_funvec, vm_desc_destroy): New static functions. (vm_desc_mark): Mark the captured bindings in vd->ftab. (vm_gcall, vm_gapply): New static functions. (vm_execute): Handle GCALL and GAPPLY opcodes. (vm_desc_ops): Wire vm_desc_destroy in place of cobj_destroy_free_op. (vm_init): Add argument to vm-make-desc intrinsic. Register vm-desc-funvec intrinsic. * vm.h (vm_make_desc): Declaration updated. * vmop.h: Regenerated
* vm: free display memory when closure reclaimed.Kaz Kylheku2018-03-181-1/+8
| | | | | | * vm.c (vm_closure_destroy): New static function. (vm_closure_ops): Use vm_closure_destroy rather than generic cobj_destroy_free_op.
* asm/vm: add ifq and ifql instructions.Kaz Kylheku2018-03-151-0/+28
| | | | | | | | | | * share/txr/stdlib/asm.tl (op-ifq, op-ifql): New opcode types. * vm.c (vm_ifq, vm_ifql): New static functions. (vm_execute): Handle IFQ and IFQL opcodes. * vmop.h (vm_op_t): Regenerated.
* vm: bugfix: handle empty data vector.Kaz Kylheku2018-03-141-1/+2
| | | | | | * vm.c (vm_make_desc): We can't call vecref_l on an empty vector, because it has no index zero. Let's use a null location in this case.
* higher level disassemble function.Kaz Kylheku2018-03-131-0/+7
| | | | | | | | | | | | | | | | * lisplib.c (asm_set_entries): Autoload on usr:disassemble. * share/txr/stdlib/asm.tl (assembler): Drop initializer from bstr slot. Requires complex initialization for the case when the buf is supplied by the constructor caller for the sake of disassembling existing code. (assembler :postinit): Handle cases when only one of buf or bstr are set, and when both are not set, for the greatest flexibility. (disassemble-c-d, disassemble): New functions. * vm.c (vm_desc_datavec): New static function. (vm_init): Registered vm-desc-datavec intrinsic.
* vm: rename vm-interpret-toplevel function.Kaz Kylheku2018-03-131-1/+1
| | | | | * vm.c (vm_init): Register vm_execute_toplevel as vm-execute-toplevel rather than vm-interpret-toplevel.
* vm: introduce sframe instruction.Kaz Kylheku2018-03-121-2/+15
| | | | | | | | | | | | | | | | | | | | This is for allocating a new frame purely on the stack. The frame will not be captured by lexical closures, and so can only be used for non-shared variables and additional compiler-generated temporaries (if registers run out, for instance). * share/txr/stdlib/asm.tl (op-sframe, sframe): New opcode class and opcode. * vm.c (vm_do_frame): New static function for the common implementation of frame and sframe. (vm_frame): Now just a call with vm_do_frame, passing the flag indicating that closure capture is enabled for this environment frame. (vm_sframe): New static function. * vmop.h: Regenerated.
* vm: use memcpy for copying environment.Kaz Kylheku2018-03-121-4/+2
| | | | | | * vm.c (vm_make_closure): When copying captured environment from stack to heap, use memcpy instead of a loop with assignments.
* New: virtual machine with assembler.Kaz Kylheku2018-03-101-0/+818
This commit is the start of compiler work to make TXR Lisp execute faster. In six days of part time work, we now have a register-style virtual machine with 32 instructions, handling exceptions, unwind-protect, lexical closures, and global environment access/mutation. We have a complete assembler and disassembler for this machine. The assembler supports labels with forward referencing with backpatching, and features pseudo-ops: for instance the (mov ...) pseudo-instruction chooses one of three kinds of specific move instruction based on the operands. * Makelfile (OBJS): Add vm.o. * eval.c (lookup_sym_lisp1): Static function becomes external; the virtual machine needs to use this to support that style of lookup. * genvmop.txr: New file. This is the generator for the "vmop.h" header. * lib.c (func_vm): New function. (generic_funcall): Handle the FVM function type via new vm_execute_closure function. In the variadic case, we want to avoid the argument copying which we do for the sake of C functions that get their fixed arguments directly, and then just the trailing arguments. Thus the code is restructured a bit in order to switch twice on the function type. (init): Call vm_init. * lib.h (functype_t): New enum member FVM. (struct func): New member in the .f union: vm_desc. (func_vm): Declared. * lisplib.c (set_dlt_entries_impl): New static function, formed from set_dlt_entries. (set_dlt_entries): Reduced to wrapper for set_dlt_entries_impl, passing in the user package. (set_dlt_entries_sys): New static function: like set_dlt_entries but targetting the sys package. (asm_instantiate, asm_set_entries): New static functions. (lisplib_init): Auto-load the sys:assembler class. * share/txr/stdlib/asm.tl: New file. * vm.c, vm.h, vmop.h: New files.