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.



734
735
736
737
738
# File 'lib/wardite.rb', line 734

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



1072
1073
1074
1075
1076
1077
1078
# File 'lib/wardite.rb', line 1072

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

Instance Attribute Details

#call_stackObject

: Array



729
730
731
# File 'lib/wardite.rb', line 729

def call_stack
  @call_stack
end

#instanceObject (readonly)

: Instance



731
732
733
# File 'lib/wardite.rb', line 731

def instance
  @instance
end

#stackObject

TODO: add types of class that the stack accomodates



727
728
729
# File 'lib/wardite.rb', line 727

def stack
  @stack
end

Instance Method Details

#call(name, args) ⇒ Object



749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
# File 'lib/wardite.rb', line 749

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)


742
743
744
# File 'lib/wardite.rb', line 742

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

#drained_stack(finish) ⇒ Object



1060
1061
1062
1063
1064
1065
1066
1067
# File 'lib/wardite.rb', line 1060

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



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
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
# File 'lib/wardite.rb', line 885

def eval_insn(frame, insn)
  case insn.namespace
  when :convert
    return Evaluator.convert_eval_insn(self, frame, insn)
  when :i32
    return Evaluator.i32_eval_insn(self, frame, insn)
  when :i64
    return Evaluator.i64_eval_insn(self, frame, insn)
  when :f32
    return Evaluator.f32_eval_insn(self, frame, insn)
  when :f64
    return Evaluator.f64_eval_insn(self, frame, insn)
  end

  # unmached namespace...
  case insn.code
  when :unreachable
    raise Unreachable, "unreachable op"
  when :nop
    return

  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 :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

  when :drop
    stack.pop

  when :select
    cond, right, left = stack.pop, stack.pop, stack.pop
    if !cond.is_a?(I32)
      raise EvalError, "invalid stack for select"
    end
    if !right || !left
      raise EvalError, "stack too short"
    end
    stack.push(cond.value != 0 ? left : right)

  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
    frame.locals[idx] = value

  when :memory_size
    memory = instance.store.memories[0] || raise("[BUG] no memory")
    stack.push(I32(memory.current))

  when :memory_grow
    delta = stack.pop
    if !delta.is_a?(I32)
      raise EvalError, "maybe stack too short"
    end
    memory = instance.store.memories[0] || raise("[BUG] no memory")
    stack.push(I32(memory.grow(delta.value)))

  else
    raise "TODO! unsupported #{insn.inspect}"
  end

rescue => e
  require "pp"
  $stderr.puts "frame:::\n#{frame.pretty_inspect}"
  $stderr.puts "stack:::\n#{stack.pretty_inspect}"
  raise e
end

#execute!Object



867
868
869
870
871
872
873
874
875
876
877
878
879
880
# File 'lib/wardite.rb', line 867

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



1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
# File 'lib/wardite.rb', line 1014

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



830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
# File 'lib/wardite.rb', line 830

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



809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
# File 'lib/wardite.rb', line 809

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



782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
# File 'lib/wardite.rb', line 782

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)


1082
1083
1084
# File 'lib/wardite.rb', line 1082

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

#stack_unwind(sp, arity) ⇒ Object

unwind the stack and put return value if exists



1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
# File 'lib/wardite.rb', line 1042

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