Class: Wardite::Runtime

Inherits:
Object
  • Object
show all
Includes:
ValueHelper
Defined in:
lib/wardite.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ValueHelper

#F32, #F64, #I32, #I64

Constructor Details

#initialize(inst) ⇒ Runtime

Returns a new instance of Runtime.



698
699
700
701
702
# File 'lib/wardite.rb', line 698

def initialize(inst)
  @stack = []
  @call_stack = []
  @instance = inst
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object



1039
1040
1041
1042
1043
1044
1045
# File 'lib/wardite.rb', line 1039

def method_missing(name, *args)
  if callable? name
    call(name, args)
  else
    super
  end
end

Instance Attribute Details

#call_stackObject

: Array



693
694
695
# File 'lib/wardite.rb', line 693

def call_stack
  @call_stack
end

#instanceObject (readonly)

: Instance



695
696
697
# File 'lib/wardite.rb', line 695

def instance
  @instance
end

#stackObject

TODO: add types of class that the stack accomodates



691
692
693
# File 'lib/wardite.rb', line 691

def stack
  @stack
end

Instance Method Details

#call(name, args) ⇒ Object



713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
# File 'lib/wardite.rb', line 713

def call(name, args)
  if !callable?(name)
    raise ::NoMethodError, "function #{name} not found"
  end
  kind, fn = @instance.exports[name.to_s]
  if kind != 0
    raise ::NoMethodError, "#{name} is not a function"
  end
  if fn.callsig.size != args.size
    raise ArgumentError, "unmatch arg size"
  end
  args.each_with_index do |arg, idx|
    case fn.callsig[idx]
    when :i32
      raise "type mismatch: i32(#{arg})" unless arg.is_a?(Integer)
      stack.push I32(arg)
    else
      raise "TODO: add me"
    end
  end

  case fn
  when WasmFunction
    invoke_internal(fn)
  when ExternalFunction
    invoke_external(fn)
  else
    raise GenericError, "registered pointer is not to a function"
  end
end

#callable?(name) ⇒ Boolean

Returns:

  • (Boolean)


706
707
708
# File 'lib/wardite.rb', line 706

def callable?(name)
  !! @instance.exports[name.to_s]
end

#drained_stack(finish) ⇒ Object



1027
1028
1029
1030
1031
1032
1033
1034
# File 'lib/wardite.rb', line 1027

def drained_stack(finish)
  drained = stack[0...finish]
  if ! drained
    $stderr.puts "warning: state of stack: #{stack.inspect}"
    raise EvalError, "stack too short"
  end
  return drained
end

#eval_insn(frame, insn) ⇒ Object



849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
# File 'lib/wardite.rb', line 849

def eval_insn(frame, insn)
  case insn.namespace
  when :i32
    return Evaluator.i32_eval_insn(self, frame, insn)
  end

  # unmached namespace...
  case insn.code
  when :if
    block = insn.operand[0]
    raise EvalError, "if op without block" if !block.is_a?(Block)
    cond = stack.pop 
    raise EvalError, "cond not found" if !cond.is_a?(I32)
    next_pc = fetch_ops_while_end(frame.body, frame.pc)
    if cond.value.zero?
      frame.pc = next_pc
    end

    label = Label.new(:if, next_pc, stack.size, block.result_size)
    frame.labels.push(label)
    
  when :local_get
    idx = insn.operand[0]
    if !idx.is_a?(Integer)
      raise EvalError, "[BUG] invalid type of operand"
    end
    local = frame.locals[idx]
    if !local
      raise EvalError, "local not found"
    end
    stack.push(local)
  when :local_set
    idx = insn.operand[0]
    if !idx.is_a?(Integer)
      raise EvalError, "[BUG] invalid type of operand"
    end
    value = stack.pop
    if !value
      raise EvalError, "value should be pushed"
    end
    if value.is_a?(Block)
      raise EvalError, "block value detected"
    end
    frame.locals[idx] = value

  # when :i32_lts
  #   right, left = stack.pop, stack.pop
  #   if !right.is_a?(Integer) || !left.is_a?(Integer)
  #     raise EvalError, "maybe empty stack"
  #   end
  #   value = (left < right) ? 1 : 0
  #   stack.push(value)
  # when :i32_leu
  #   right, left = stack.pop, stack.pop
  #   if !right.is_a?(Integer) || !left.is_a?(Integer)
  #     raise EvalError, "maybe empty stack"
  #   end
  #   value = (left >= right) ? 1 : 0
  #   stack.push(value)
  # when :i32_add
  #   right, left = stack.pop, stack.pop
  #   if !right.is_a?(Integer) || !left.is_a?(Integer)
  #     raise EvalError, "maybe empty stack"
  #   end
  #   stack.push(left + right)
  # when :i32_sub
  #   right, left = stack.pop, stack.pop
  #   if !right.is_a?(Integer) || !left.is_a?(Integer)
  #     raise EvalError, "maybe empty stack"
  #   end
  #   stack.push(left - right)
  # when :i32_const
  #   const = insn.operand[0]
  #   if !const.is_a?(Integer)
  #     raise EvalError, "[BUG] invalid type of operand"
  #   end
  #   stack.push(const)
  # when :i32_store
  #   _align = insn.operand[0] # TODO: alignment support?
  #   offset = insn.operand[1]
  #   raise EvalError, "[BUG] invalid type of operand" if !offset.is_a?(Integer)

  #   value = stack.pop
  #   addr = stack.pop
  #   if !value.is_a?(Integer) || !addr.is_a?(Integer)
  #     raise EvalError, "maybe stack too short"
  #   end

  #   at = addr + offset
  #   data_end = at + 4 # sizeof(i32)
  #   memory = self.instance.store.memories[0] || raise("[BUG] no memory")
  #   memory.data[at...data_end] = [value].pack("I")

  when :call
    idx = insn.operand[0]
    raise EvalError, "[BUG] local operand not found" if !idx.is_a?(Integer)
    fn = self.instance.store.funcs[idx]
    case fn
    when WasmFunction
      push_frame(fn)
    when ExternalFunction
      ret = invoke_external(fn)
      self.stack.push ret if ret
    else
      raise GenericError, "got a non-function pointer"
    end

  when :return
    old_frame = call_stack.pop
    if !old_frame
      raise EvalError, "maybe empty call stack"
    end

    stack_unwind(old_frame.sp, old_frame.arity)

  when :end
    if old_label = frame.labels.pop
      frame.pc = old_label.pc
      stack_unwind(old_label.sp, old_label.arity)
    else
      old_frame = call_stack.pop
      if !old_frame
        raise EvalError, "maybe empty call stack"
      end
      stack_unwind(old_frame.sp, old_frame.arity)
    end
  end
end

#execute!Object



831
832
833
834
835
836
837
838
839
840
841
842
843
844
# File 'lib/wardite.rb', line 831

def execute!
  loop do
    cur_frame = self.call_stack.last #: Frame
    if !cur_frame
      break
    end
    cur_frame.pc += 1
    insn = cur_frame.body[cur_frame.pc]
    if !insn
      break
    end
    eval_insn(cur_frame, insn)
  end
end

#fetch_ops_while_end(ops, pc_start) ⇒ Object



981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
# File 'lib/wardite.rb', line 981

def fetch_ops_while_end(ops, pc_start)
  cursor = pc_start
  depth = 0
  loop {
    cursor += 1
    inst = ops[cursor]
    case inst&.code
    when nil
      raise EvalError, "end op not found"
    when :i
      depth += 1
    when :end
      if depth == 0
        return cursor
      else
        depth -= 1
      end
    else
      # nop
    end
  }
  raise "[BUG] unreachable"
end

#invoke_external(external_function) ⇒ Object



794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
# File 'lib/wardite.rb', line 794

def invoke_external(external_function)
  local_start = stack.size - external_function.callsig.size
  args = stack[local_start..]
  if !args
    raise LoadError, "stack too short"
  end
  self.stack = drained_stack(local_start)

  proc = external_function.callable
  val = proc[self.instance.store, args]
  if !val
    return
  end

  case val
  when I32, I64, F32, F64
    return val
  when Integer
    case external_function.retsig[0]
    when :i32
      return I32(val)
    when :i64
      return I64(val)
    end
  when Float
    case external_function.retsig[0]
    when :f32
      return F32(val)
    when :f64
      return F64(val)
    end
  end

  raise "invalid type of value returned in proc. val: #{val.inspect}"
end

#invoke_internal(wasm_function) ⇒ Object



773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
# File 'lib/wardite.rb', line 773

def invoke_internal(wasm_function)
  arity = wasm_function.retsig.size
  push_frame(wasm_function)
  execute!

  if arity > 0
    if arity > 1
      raise ::NotImplementedError, "return artiy >= 2 not yet supported ;;"
    end
    if self.stack.empty?
      raise "[BUG] stack empry"
    end
    v = self.stack.pop
    return v
  end

  return nil
end

#push_frame(wasm_function) ⇒ Object



746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
# File 'lib/wardite.rb', line 746

def push_frame(wasm_function)
  local_start = stack.size - wasm_function.callsig.size
  locals = stack[local_start..]
  if !locals
    raise LoadError, "stack too short"
  end
  self.stack = drained_stack(local_start)

  wasm_function.locals_type.each_with_index do |typ, i|
    case typ
    when :i32, :u32
      locals.push I32(0)
    when :i64, :u64
      locals.push I64(0)
    else
      $stderr.puts "warning: unknown type #{typ.inspect}. default to I32"
      locals.push I32(0)
    end
  end

  arity = wasm_function.retsig.size
  frame = Frame.new(-1, stack.size, wasm_function.body, arity, locals)
  self.call_stack.push(frame)
end

#respond_to?(name) ⇒ Boolean

Returns:

  • (Boolean)


1049
1050
1051
# File 'lib/wardite.rb', line 1049

def respond_to? name
  callable?(name) || super
end

#stack_unwind(sp, arity) ⇒ Object

unwind the stack and put return value if exists



1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
# File 'lib/wardite.rb', line 1009

def stack_unwind(sp, arity)
  if arity > 0
    if arity > 1
      raise ::NotImplementedError, "return artiy >= 2 not yet supported ;;"
    end
    value = stack.pop
    if !value
      raise EvalError, "cannot obtain return value"
    end
    self.stack = drained_stack(sp)
    stack.push value
  else
    self.stack = drained_stack(sp)
  end
end