Class: Wardite::Runtime

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(inst) ⇒ Runtime

Returns a new instance of Runtime.



685
686
687
688
689
# File 'lib/wardite.rb', line 685

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



1012
1013
1014
1015
1016
1017
1018
# File 'lib/wardite.rb', line 1012

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

Instance Attribute Details

#call_stackObject

: Array



680
681
682
# File 'lib/wardite.rb', line 680

def call_stack
  @call_stack
end

#instanceObject (readonly)

: Instance



682
683
684
# File 'lib/wardite.rb', line 682

def instance
  @instance
end

#stackObject

: Array



678
679
680
# File 'lib/wardite.rb', line 678

def stack
  @stack
end

Instance Method Details

#call(name, args) ⇒ Object



700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
# File 'lib/wardite.rb', line 700

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 do |arg|
    stack.push arg
  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

#call_index(idx, args) ⇒ Object



728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
# File 'lib/wardite.rb', line 728

def call_index(idx, args)
  fn = self.instance.store[idx]
  if !fn
    # TODO: own error NoFunctionError
    raise ::NoMethodError, "func #{idx} not found"
  end
  if fn.callsig.size != args.size
    raise ArgumentError, "unmatch arg size"
  end
  args.each do |arg|
    stack.push arg
  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)


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

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

#drained_stack(finish) ⇒ Object



1000
1001
1002
1003
1004
1005
1006
1007
# File 'lib/wardite.rb', line 1000

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



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
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
# File 'lib/wardite.rb', line 831

def eval_insn(frame, insn)
  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?(Integer)
    next_pc = fetch_ops_while_end(frame.body, frame.pc)
    if cond.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
    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



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

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



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 954

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



800
801
802
803
804
805
806
807
808
809
810
# File 'lib/wardite.rb', line 800

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
  proc[self.instance.store, args]
end

#invoke_internal(wasm_function) ⇒ Object



779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
# File 'lib/wardite.rb', line 779

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



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

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 Local::I32(typ, 0)...
      locals.push 0
    else
      $stderr.puts "warning: unknown type #{typ.inspect}. default to Object"
      locals.push Object.new
    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)


1022
1023
1024
# File 'lib/wardite.rb', line 1022

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

#stack_unwind(sp, arity) ⇒ Object

unwind the stack and put return value if exists



982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
# File 'lib/wardite.rb', line 982

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