def store_results(stack_effect): ''' Returns a Code to write the results from C variables into the stack, skipping 'ITEMS'. - stack_effect - StackEffect `stack_depth` must be modified first. ''' code = Code() for item in stack_effect.results.items: if item.name != 'ITEMS': code.extend(store_item(item)) return code
def load_args(stack_effect): ''' Returns a Code to read the arguments from the stack into C variables, skipping 'ITEMS' and 'COUNT'. - stack_effect - StackEffect `stack_depth` is not modified. ''' code = Code() for item in stack_effect.args.items: if item.name != 'ITEMS' and item.name != 'COUNT': code.extend(load_item(item)) return code
def add(self, depth_change): ''' Returns a Code to update the variable `cached_depth` to reflect a change in the stack depth, e.g. by pushing or popping some items. Also updates `self`. - depth_change - int (N.B. not Size) ''' assert type(depth_change) is int if depth_change == 0: return Code() self.cached_depth += depth_change if self.cached_depth < 0: self.cached_depth = 0 self.checked_depth -= depth_change if self.checked_depth < 0: self.checked_depth = 0 return Code(f'cached_depth = {self.cached_depth};')
def run_body(instructions): ''' Compute the instruction dispatch code for an inner run function. ''' return dispatch( instructions, Code('// Undefined instruction.', 'THROW(MIT_ERROR_INVALID_OPCODE);'), gen_code=gen_instruction_code, )
def run_inner_fn(instructions, suffix, instrument): ''' Generate a `run_inner` function. - instructions - ActionEnum - instruction set. - suffix - str - the function is named `run_inner_{suffix}`. - instrument - Code or str - instrumentation to insert at start of main loop. ''' return disable_warnings( ['-Wstack-protector', '-Wvla-larger-than=' ], # TODO: Stack protection cannot cope with VLAs. Code( f'''\ // Define run_inner for the benefit of `call`. #define run_inner run_inner_{suffix} static void run_inner_{suffix}(mit_word_t *pc, mit_word_t ir, mit_word_t * restrict stack, mit_uword_t stack_words, mit_uword_t * restrict stack_depth_ptr, jmp_buf *jmp_buf_ptr) {{''', Code(*[ '''\ #define stack_depth (*stack_depth_ptr) mit_word_t error; for (;;) {''', instrument, Code('''\ uint8_t opcode = (uint8_t)ir; ir = ARSHIFT(ir, 8); // Check stack_depth is valid if (stack_depth > stack_words) THROW(MIT_ERROR_STACK_OVERFLOW);'''), run_body(instructions), '''\ #undef stack_depth }''', ]), '''\ error: longjmp(*jmp_buf_ptr, error); } #undef run_inner''', ))
def declare_vars(stack_effect): ''' Returns a Code to declare C variables for arguments and results other than 'ITEMS'. - stack_effect - StackEffect ''' return Code(*[ f'{item.type} {item.name};' for item in stack_effect.by_name.values() if item.name != 'ITEMS' ])
def check_overflow(num_pops, num_pushes): ''' Returns a Code to check that the stack contains enough space to push `num_pushes` items, given that `num_pops` items will first be popped successfully. - num_pops - Size. - num_pushes - Size. `num_pops` and `num_pushes` must both be variadic or both not. ''' assert isinstance(num_pops, Size) assert isinstance(num_pushes, Size) assert num_pops >= 0 assert num_pushes >= 0 depth_change = num_pushes - num_pops if depth_change <= 0: return Code() # Ensure comparison will not overflow assert depth_change.count == 0 return Code(f'''\ if (unlikely(stack_words - stack_depth < {depth_change})) THROW(MIT_ERROR_STACK_OVERFLOW);''')
def store_results(self, results): ''' Returns a Code to write the results from C variables into the stack. `stack_depth` must be modified first. - results - list of str. ''' return Code(*[ f'{self.lvalue(pos)} = {item.name};' for pos, item in enumerate(reversed(results.items)) ])
def check_underflow(num_pops): ''' Returns a Code to check that the stack contains enough items to pop the specified number of items. - num_pops - Size, with non-negative `count`. ''' assert isinstance(num_pops, Size) assert num_pops >= 0, num_pops if num_pops == 0: return Code() tests = [] tests.append(f'unlikely(stack_depth < (mit_uword_t)({num_pops.size}))') if num_pops.count == 1: tests.append( f'unlikely(stack_depth - (mit_uword_t)({num_pops.size}) < (mit_uword_t)(COUNT))' ) return Code( 'if ({}) {{'.format(' || '.join(tests)), Code('THROW(MIT_ERROR_INVALID_STACK_READ);'), '}', )
def load_args(self, args): ''' Returns a Code to read the arguments from the stack into C variables. `stack_depth` is not modified. - args - list of str. ''' return Code(*[ f'{item.name} = {self.lvalue(pos)};' for pos, item in enumerate(reversed(args.items)) ])
def __init__(self, opcode, library, includes): super().__init__(Action( None, Code( '''\ { mit_word_t function;''', pop_stack('function'), f''' int ret = trap_{self.name.lower()}(function, stack, &stack_depth); if (ret != 0) THROW(ret); }}''')), opcode=opcode) self.library = library self.includes = includes
def gen_instruction_code(instruction): ''' Generate a Code for an Instruction. This is suitable for passing as the `gen_code` argument of `dispatch()`. ''' code = gen_action_code(instruction.action) if instruction.terminal is not None: ir_all_bits = 0 if instruction.opcode & 0x80 == 0 else -1 code = Code( f'if (ir != {ir_all_bits}) {{', gen_action_code(instruction.terminal), '} else {', code, '}', ) return code
def run_fn(suffix): ''' Generate a `mit_run`-like function. - suffix - str - the function is named `mit_run_{suffix}` and will call an inner function `run_inner_{suffix}`. ''' return Code( f''' mit_word_t mit_run_{suffix}(mit_word_t *pc, mit_word_t ir, mit_word_t * restrict stack, mit_uword_t stack_words, mit_uword_t *stack_depth_ptr) {{ jmp_buf env; mit_word_t error = (mit_word_t)setjmp(env); if (error == 0) {{ run_inner_{suffix}(pc, ir, stack, stack_words, stack_depth_ptr, &env); error = MIT_ERROR_OK; }} return error; }} ''', )
def flush(self, goal=0): ''' Decrease the number of stack items that are cached in C variables, if necessary. Returns a Code to move values between variables and to memory. Also updates the C variable `cached_depth`. - goal - a CacheState to match, or an int to specify a desired `cache_depth`. Default is `0`. ''' if type(goal) is int: goal = CacheState(goal, self.checked_depth) assert goal.cached_depth <= self.cached_depth, (goal, self) assert goal.checked_depth <= self.checked_depth, (goal, self) self.checked_depth = goal.checked_depth if goal.cached_depth == self.cached_depth: return Code() code = Code() for pos in reversed(range(self.cached_depth)): code.append(f'{goal.lvalue(pos)} = {self.lvalue(pos)};') self.cached_depth = goal.cached_depth code.append(f'cached_depth = {self.cached_depth};') return code
def store_stack(value, depth=0, type='mit_word_t'): ''' Generate C code to store the value `value` of type `type` occupying stack slots `depth`, `depth+1`, ... . Does not check the stack. Returns a Code. ''' code = Code() code.extend( disable_warnings( ['-Wpointer-to-int-cast', '-Wbad-function-cast'], Code( f'mit_max_stack_item_t temp = (mit_max_stack_item_t){value};'), )) for i in reversed(range(1, type_words(type))): code.append( f'*mit_stack_pos(stack, stack_depth, {depth + i}) = (mit_uword_t)(temp & MIT_UWORD_MAX);' ) code.append('temp >>= MIT_WORD_BIT;') code.append( '*mit_stack_pos(stack, stack_depth, {}) = (mit_uword_t)({});'.format( depth, 'temp & MIT_UWORD_MAX' if type_words(type) > 1 else 'temp', )) return Code('{', code, '}')
def gen_case(instruction, cache_state): ''' Generate a Code for a member of Instruction. It is the caller's responsibility to ensure that it's the right instruction to execute, and that the stack won't underflow or overflow. In the code, errors are reported by calling THROW(). When calling THROW(), the C variable `cached_depth` will contain the number of stack items cached in C locals. - instruction - Instructions. - cache_state - CacheState - Which StackItems are cached. Updated in place. ''' code = Code() num_args = len(instruction.action.effect.args.items) num_results = len(instruction.action.effect.results.items) # Declare C variables for args and results. code.extend(Code(*[ f'mit_word_t {name};' for name, item in instruction.action.effect.by_name.items() ])) # Load the arguments into their C variables. code.extend(cache_state.load_args(instruction.action.effect.args)) # Inline `instruction.action.code`. # Note: `stack_depth` and `cache_state` must be correct for THROW(). code.extend(instruction.action.code) # Update stack pointer and cache_state. code.append(f'stack_depth -= {num_args};') code.extend(cache_state.add(-num_args)) code.extend(cache_state.add(num_results)) code.append(f'stack_depth += {num_results};') # Store the results from their C variables. code.extend(cache_state.store_results(instruction.action.effect.results)) return code
class ExtraInstructions(ActionEnum): '''VM extra instructions.''' DIVMOD = ( Action( StackEffect.of(['a', 'b'], ['q', 'r']), Code('''\ if (b == 0) THROW(MIT_ERROR_DIVISION_BY_ZERO); q = a / b; r = a % b; ''')), 0x1, ) UDIVMOD = ( Action( StackEffect.of(['a', 'b'], ['q', 'r']), Code('''\ if (b == 0) THROW(MIT_ERROR_DIVISION_BY_ZERO); q = (mit_word_t)((mit_uword_t)a / (mit_uword_t)b); r = (mit_word_t)((mit_uword_t)a % (mit_uword_t)b); ''')), 0x2, ) THROW = ( Action( None, # Manage stack manually so that `stack_depth` is # decremented before THROW(). Code('''\ POP(n); THROW(n); '''), ), 0x3, ) CATCH = ( Action( None, # Manage stack manually because the stack module doesn't # understand multiple stack frames. Code('''\ POP(addr); if (unlikely(addr % sizeof(mit_word_t) != 0)) THROW(MIT_ERROR_UNALIGNED_ADDRESS); DO_CATCH(addr); '''), ), 0x4, ) ARGC = ( Action( StackEffect.of([], ['argc']), Code('argc = (mit_word_t)mit_argc;'), ), 0x100, ) ARGV = ( Action( StackEffect.of([], ['argv:char **']), Code('argv = mit_argv;'), ), 0x101, )
), 0x100, ) ARGV = ( Action( StackEffect.of([], ['argv:char **']), Code('argv = mit_argv;'), ), 0x101, ) # Inject code for EXTRA extra_code = Code('''\ mit_uword_t extra_opcode = ir; ir = 0; ''') extra_code.extend( dispatch( ExtraInstructions, Code('THROW(MIT_ERROR_INVALID_OPCODE);', ), 'extra_opcode', )) # Core instructions. @dataclass class Instruction: ''' VM instruction descriptor.
def push_stack(value, type='mit_word_t'): code = Code() code.extend(check_overflow(Size(0), Size(type_words(type)))) code.extend(store_stack(value, depth=-type_words(type), type=type)) code.append(f'stack_depth += {type_words(type)};') return code
def load_stack(name, depth=0, type='mit_word_t'): ''' Generate C code to load the variable `name` of type `type` occupying stack slots `depth`, `depth+1`, ... . Does not check the stack. Returns a Code. ''' code = Code() code.append( f'mit_max_stack_item_t temp = (mit_uword_t)(*mit_stack_pos(stack, stack_depth, {depth}));' ) for i in range(1, type_words(type)): code.append('temp <<= MIT_WORD_BIT;') code.append( f'temp |= (mit_uword_t)(*mit_stack_pos(stack, stack_depth, {depth + i}));' ) code.extend( disable_warnings( ['-Wint-to-pointer-cast'], Code(f'{name} = ({type})temp;'), )) return Code('{', code, '}')
def gen_action_code(action): ''' Generate a Code for an Action. This is suitable for passing as the `gen_code` argument of `dispatch()`. ''' effect = action.effect code = Code() if effect is not None: # Load the arguments into C variables. code.extend(declare_vars(effect)) count = effect.args.by_name.get('COUNT') if count is not None: # If we have COUNT, check its stack position is valid, and load it. # We actually check `effect.args.size.size` (more than we need), # because this check will be generated anyway by the next # check_underflow call, so the compiler can elide one check. code.extend(check_underflow(Size(effect.args.size.size))) code.extend(load_item(count)) code.extend(check_underflow(effect.args.size)) code.extend(check_overflow(effect.args.size, effect.results.size)) code.extend(load_args(effect)) code.extend(action.code) if effect is not None: # Store the results from C variables. code.append( f'stack_depth += {effect.results.size - effect.args.size};') code.extend(store_results(effect)) return code
def dispatch(actions, undefined_case, opcode='opcode', gen_code=gen_action_code): ''' Generate dispatch code for an ActionEnum. - actions - ActionEnum. - undefined_case - Code - the fallback behaviour. - opcode - str - a C expression for the opcode. - gen_code - function - a function that takes an ActionEnum instance and returns C code to implement it. In the code, errors are reported by calling THROW(). ''' assert isinstance(undefined_case, Code), undefined_case code = Code() else_text = '' for (_, value) in enumerate(actions): opcode_symbol = f'{c_symbol(actions.__name__)}_{value.name}' code.append(f'{else_text}if ({opcode} == {opcode_symbol}) {{') code.append(gen_code(value.action)) code.append('}') else_text = 'else ' code.append(f'{else_text}{{') code.append(undefined_case) code.append('}') return code
class LibC(ActionEnum): 'Function codes for the LIBC trap.' STRLEN = Action( StackEffect.of(['s:const char *'], ['len']), Code('''\ len = (mit_word_t)(mit_uword_t)strlen(s); ''')) STRNCPY = Action( StackEffect.of(['dest:char *', 'src:const char *', 'n'], ['ret:char *']), Code('ret = strncpy(dest, src, (size_t)n);'), ) STDIN = Action(StackEffect.of([], ['fd:int']), Code('''\ fd = (mit_word_t)STDIN_FILENO; ''')) STDOUT = Action(StackEffect.of([], ['fd:int']), Code('''\ fd = (mit_word_t)STDOUT_FILENO; ''')) STDERR = Action(StackEffect.of([], ['fd:int']), Code('''\ fd = (mit_word_t)STDERR_FILENO; ''')) O_RDONLY = Action(StackEffect.of([], ['flag']), Code('''\ flag = (mit_word_t)O_RDONLY; ''')) O_WRONLY = Action(StackEffect.of([], ['flag']), Code('''\ flag = (mit_word_t)O_WRONLY; ''')) O_RDWR = Action(StackEffect.of([], ['flag']), Code('''\ flag = (mit_word_t)O_RDWR; ''')) O_CREAT = Action(StackEffect.of([], ['flag']), Code('''\ flag = (mit_word_t)O_CREAT; ''')) O_TRUNC = Action(StackEffect.of([], ['flag']), Code('''\ flag = (mit_word_t)O_TRUNC; ''')) OPEN = Action( StackEffect.of(['str:char *', 'flags'], ['fd:int']), Code('''\ fd = open(str, flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd >= 0) set_binary_mode(fd, O_BINARY); // Best effort ''')) CLOSE = Action( StackEffect.of(['fd:int'], ['ret:int']), Code('ret = (mit_word_t)close(fd);'), ) READ = Action( StackEffect.of(['buf:void *', 'nbytes', 'fd:int'], ['nread:int']), Code('nread = read(fd, buf, nbytes);'), ) WRITE = Action( StackEffect.of(['buf:void *', 'nbytes', 'fd:int'], ['nwritten']), Code('nwritten = write(fd, buf, nbytes);'), ) SEEK_SET = Action( StackEffect.of([], ['whence']), Code('''\ whence = (mit_word_t)SEEK_SET; ''')) SEEK_CUR = Action( StackEffect.of([], ['whence']), Code('''\ whence = (mit_word_t)SEEK_CUR; ''')) SEEK_END = Action( StackEffect.of([], ['whence']), Code('''\ whence = (mit_word_t)SEEK_END; ''')) LSEEK = Action( StackEffect.of(['fd:int', 'offset:off_t', 'whence'], ['pos:off_t']), Code('pos = lseek(fd, offset, whence);'), ) FDATASYNC = Action( StackEffect.of(['fd:int'], ['ret:int']), Code('ret = fdatasync(fd);'), ) RENAME = Action( StackEffect.of(['old_name:char *', 'new_name:char *'], ['ret:int']), Code('ret = rename(old_name, new_name);'), ) REMOVE = Action(StackEffect.of(['name:char *'], ['ret:int']), Code('ret = remove(name);')) # TODO: Expose stat(2). This requires struct mapping! FILE_SIZE = Action( StackEffect.of(['fd:int'], ['size:off_t', 'ret:int']), Code('''\ { struct stat st; ret = fstat(fd, &st); size = st.st_size; } '''), ) RESIZE_FILE = Action( StackEffect.of(['size:off_t', 'fd:int'], ['ret:int']), Code('ret = ftruncate(fd, size);'), ) FILE_STATUS = Action( StackEffect.of(['fd:int'], ['mode:mode_t', 'ret:int']), Code('''\ { struct stat st; ret = fstat(fd, &st); mode = st.st_mode; } '''), )
def pop_stack(name, type='mit_word_t'): code = Code() code.extend(check_underflow(Size(type_words(type)))) code.extend(load_stack(name, type=type)) code.append(f'stack_depth -= {type_words(type)};') return code