Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
Tags

hsfzxjy

1
Posts
1
Topics
A member registered Aug 02, 2023

Recent community posts

(1 edit)

This is a solution with least cycles (989) on Steam at present.

Some highlights:

  • Specialized instruction sequences to multiply a number by 10 within only 7 cycles
  • Variables are stored next to the text section (where the compiled bytecodes lie), so that they can be reset together with one go
  • Particular variables are stored in a shared memory location to reduce program length
  • Most constants are inlined into unused 3-rd operator to reduce program length


; Parse a self-modifying program containing .data directives and
; subleq instructions, then execute that program.
;
; Writes to address @OUT should be directly written out. If the
; program branches to address @HALT, then the program is done. Start
; over from scratch with the next input program.
;
; The compiled size of each input program is <= 21 bytes.
;
; As in previous tasks, the .data directive will always have exactly
; 1 value and subleq instructions will specify exactly 3 addresses
; (separated by spaces only) and no labels will be used.
;
; ===> text section to store compiled bytecodes
@text: 
.data 0 0 @PRE_START 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ; ; ===> variable section to store VM states @x: ; used by tokenizer to store the number being read .data 0 @c: ; used by tokenizer to store the last read character .data 0 ; ; The following 2 variables share the same memory location @y: ; used by tokenizer as a tmp var to do multiplication @val: ; used by VM evaluator as a tmp var to load operation values .data 0 ; ; The following 3 variables share the same memory location @sign: ; used by tokenizer. sign == 0 means x should be negated before stored; 1 otherwise. @n_x: ; used by tokenizer as a tmp var to negate x @offset: ; used by VM evaluator to store the offset of current instruction .data 0 ; ; ===> a constant to mark the boundary of text/variable section @n_vm_states_boundary: .data -@n_vm_states_boundary+1 ; @PRE_START: subleq @2:2, 2 ; ; ============= Tokenizer & Parser Block ============= @READ_LINE: ; subleq @c, @n_3:@IN ; read the first char ch of a line ; c = -ch = 0 (\0), -10 (\n), -46 (.), -115 (s) subleq @c, @n_dot, @IS_S_OR_DOT ; +46, jump if ch is '.' or 's' ; c = 46 (\0), c = 36 (\n) subleq @c, @36, @READ_LINE ; -36, jump if ch is '.' ; c = 10 (\0), end of file, goto EVAL block subleq @ptr1, @ptr1 ; reset storage pointers ptr1, ptr2 subleq @ptr2, @ptr2, @EVAL ; we don't clear c here to save one instruction, instead we clear c after EVAL ends. ; @IS_S_OR_DOT: ; c = 0 (.), x < 0 (s) subleq @c, @n_1, @IS_S ; +1, jump if ch is 's' subleq @c, @c, @READ_AFTER_DOT ; clear x and read 5 more chars @IS_S: subleq @c, @c ; clear x and read 6 more chars subleq @IN, @IN @READ_AFTER_DOT: subleq @IN, @IN subleq @IN, @IN subleq @IN, @IN subleq @IN, @IN subleq @IN, @IN, @TRY_READ_NUM ; @TRY_READ_NUM: ; subleq @c, @IN subleq @c, @n_ascii_0, @STORE_DIGIT ; @STORE_SIGN: subleq @sign, @n_1, @1:1 subleq @c, @c, @TRY_READ_NUM ; @STORE_DIGIT: subleq @x, @c subleq @c, @c ; @READ_NUM_NEXT_DIGIT: ; subleq @c, @IN subleq @c, @n_ascii_0, @MUL10 ; +48, jump if c is -'0'..-'9' ; ; Otherwise, store x into memory subleq @sign, @OUT, @IS_NEG ; @IS_POS: subleq @ptr1:@text, @x subleq @x, @x, @CLEANUP ; @IS_NEG: subleq @n_x, @x subleq @ptr2:@text, @n_x ; @CLEANUP: subleq @ptr1, @n_1, @16:16 subleq @ptr2, @n_1, @36:36 subleq @x, @x subleq @sign, @sign ; ; c == -32 (' ') + 48 = 16, the line is not ended, try read next num ; c == -10 (\n ) + 48 = 38, the line is ended, read next line subleq @c, @16, @TRY_READ_NUM ; -16, jump if last read is ' ' subleq @c, @22, @READ_LINE ; -22, jump if last read is '\n' ; ; @MUL10: ; multiply x inplace by 10. This is faster than with a loop subleq @y, @x; y = -n, x = n subleq @y, @x; y = -2n, x = n subleq @y, @x; y = -3n, x = n subleq @x, @y; y = -3n, x = 4n subleq @x, @y; y = -3n, x = 7n subleq @x, @y; y = -3n, x = 10n subleq @y, @y; ; add x by c subleq @x, @c subleq @c, @c, @READ_NUM_NEXT_DIGIT ; ; ============= VM Evaluation Block ============= ; @EVAL: @EVAL_NEXT_INSTRUCTION: subleq @ptr_1, @ptr_1 subleq @ptr_1, @offset subleq @ptr_2, @ptr_2 subleq @ptr_2, @offset subleq @ptr_2, @n_1, @n_1:255 ; @LOAD_OP1: subleq @val, @ptr_1:0, @OP1_IS_ADDR ; jump if [@ptr_1] >= 0 ; @OP1_IS_254: subleq @op1, @op1 subleq @op1, @2, @CLEAN_VAL_AND_LOAD_OP2 ; @OP1_IS_ADDR: subleq @op1, @op1 subleq @op1, @val ; @CLEAN_VAL_AND_LOAD_OP2: subleq @val, @val, @LOAD_OP2 ; @LOAD_OP2: subleq @val, @ptr_2:0 subleq @op2, @op2 subleq @op2, @val ; @DO_EVAL: subleq @op1:0, @op2:0, @BRANCH ; @NOT_BRANCH: ; directly advance ptr_1, ptr_2, offset by 3, instead of goto @EVAL_NEXT_INSTRUCTION ; to save several cycles subleq @ptr_1, @n_3, @n_dot: 210; -'.' subleq @ptr_2, @n_3, @n_ascii_0: 208; -'0' subleq @offset, @3 subleq @val, @val, @LOAD_OP1 ; @BRANCH: subleq @ptr_3, @ptr_3 subleq @ptr_3, @offset subleq @ptr_3, @n_2, @n_2:254 subleq @offset, @offset subleq @offset, @ptr_3:0, @EVAL_CURRENT_INSTRUCTION_END ; jump if [@ptr_3] >= 0 ; ; Now we are branching to 255, which should halt and reset VM @RESET_VM: subleq @ptr3, @n_vm_states_boundary, @3:3 subleq @ptr4, @n_vm_states_boundary, @22:22 @RESET_NEXT_STATE: subleq @ptr3:0, @ptr4:0 subleq @ptr3, @1 subleq @ptr4, @1, @RESET_ADDR_0 subleq @x, @x, @RESET_NEXT_STATE ; @RESET_ADDR_0: subleq 0, 0, @READ_LINE ; ; @EVAL_CURRENT_INSTRUCTION_END: subleq @y, @y, @EVAL_NEXT_INSTRUCTION