github.com/taubyte/vm-wasm-utils@v1.0.2/binary/element.go (about)

     1  package binary
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  
     8  	wasm "github.com/taubyte/vm-wasm-utils"
     9  	"github.com/taubyte/vm-wasm-utils/leb128"
    10  )
    11  
    12  func ensureElementKindFuncRef(r *bytes.Reader) error {
    13  	elemKind, err := r.ReadByte()
    14  	if err != nil {
    15  		return fmt.Errorf("read element prefix: %w", err)
    16  	}
    17  	if elemKind != 0x0 { // ElemKind is fixed to 0x0 now: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
    18  		return fmt.Errorf("element kind must be zero but was 0x%x", elemKind)
    19  	}
    20  	return nil
    21  }
    22  
    23  func decodeElementInitValueVector(r *bytes.Reader) ([]*wasm.Index, error) {
    24  	vs, _, err := leb128.DecodeUint32(r)
    25  	if err != nil {
    26  		return nil, fmt.Errorf("get size of vector: %w", err)
    27  	}
    28  
    29  	vec := make([]*wasm.Index, vs)
    30  	for i := range vec {
    31  		u32, _, err := leb128.DecodeUint32(r)
    32  		if err != nil {
    33  			return nil, fmt.Errorf("read function index: %w", err)
    34  		}
    35  		vec[i] = &u32
    36  	}
    37  	return vec, nil
    38  }
    39  
    40  func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, enabledFeatures wasm.Features) ([]*wasm.Index, error) {
    41  	vs, _, err := leb128.DecodeUint32(r)
    42  	if err != nil {
    43  		return nil, fmt.Errorf("failed to get the size of constexpr vector: %w", err)
    44  	}
    45  	vec := make([]*wasm.Index, vs)
    46  	for i := range vec {
    47  		expr, err := decodeConstantExpression(r, enabledFeatures)
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  		switch expr.Opcode {
    52  		case wasm.OpcodeRefFunc:
    53  			if elemType != wasm.RefTypeFuncref {
    54  				return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has funcref", wasm.RefTypeName(elemType))
    55  			}
    56  			v, _, _ := leb128.DecodeUint32(bytes.NewReader(expr.Data))
    57  			vec[i] = &v
    58  		case wasm.OpcodeRefNull:
    59  			if elemType != expr.Data[0] {
    60  				return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has %s",
    61  					wasm.RefTypeName(elemType), wasm.RefTypeName(expr.Data[0]))
    62  			}
    63  			// vec[i] is already nil, so nothing to do.
    64  		default:
    65  			return nil, fmt.Errorf("const expr must be either ref.null or ref.func but was %s", wasm.InstructionName(expr.Opcode))
    66  		}
    67  	}
    68  	return vec, nil
    69  }
    70  
    71  func decodeElementRefType(r *bytes.Reader) (ret wasm.RefType, err error) {
    72  	ret, err = r.ReadByte()
    73  	if err != nil {
    74  		err = fmt.Errorf("read element ref type: %w", err)
    75  		return
    76  	}
    77  	if ret != wasm.RefTypeFuncref && ret != wasm.RefTypeExternref {
    78  		return 0, errors.New("ref type must be funcref or externref for element as of WebAssembly 2.0")
    79  	}
    80  	return
    81  }
    82  
    83  const (
    84  	// The prefix is explained at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
    85  
    86  	// elementSegmentPrefixLegacy is the legacy prefix and is only valid one before FeatureBulkMemoryOperations.
    87  	elementSegmentPrefixLegacy = iota
    88  	// elementSegmentPrefixPassiveFuncrefValueVector is the passive element whose indexes are encoded as vec(varint), and reftype is fixed to funcref.
    89  	elementSegmentPrefixPassiveFuncrefValueVector
    90  	// elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex is the same as elementSegmentPrefixPassiveFuncrefValueVector but active and table index is encoded.
    91  	elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex
    92  	// elementSegmentPrefixDeclarativeFuncrefValueVector is the same as elementSegmentPrefixPassiveFuncrefValueVector but declarative.
    93  	elementSegmentPrefixDeclarativeFuncrefValueVector
    94  	// elementSegmentPrefixActiveFuncrefConstExprVector is active whoce reftype is fixed to funcref and indexes are encoded as vec(const_expr).
    95  	elementSegmentPrefixActiveFuncrefConstExprVector
    96  	// elementSegmentPrefixPassiveConstExprVector is passive whoce indexes are encoded as vec(const_expr), and reftype is encoded.
    97  	elementSegmentPrefixPassiveConstExprVector
    98  	// elementSegmentPrefixPassiveConstExprVector is active whoce indexes are encoded as vec(const_expr), and reftype and table index are encoded.
    99  	elementSegmentPrefixActiveConstExprVector
   100  	// elementSegmentPrefixDeclarativeConstExprVector is declarative whoce indexes are encoded as vec(const_expr), and reftype is encoded.
   101  	elementSegmentPrefixDeclarativeConstExprVector
   102  )
   103  
   104  func decodeElementSegment(r *bytes.Reader, enabledFeatures wasm.Features) (*wasm.ElementSegment, error) {
   105  	prefix, _, err := leb128.DecodeUint32(r)
   106  	if err != nil {
   107  		return nil, fmt.Errorf("read element prefix: %w", err)
   108  	}
   109  
   110  	if prefix != elementSegmentPrefixLegacy {
   111  		if err := enabledFeatures.Require(wasm.FeatureBulkMemoryOperations); err != nil {
   112  			return nil, fmt.Errorf("non-zero prefix for element segment is invalid as %w", err)
   113  		}
   114  	}
   115  
   116  	// Encoding depends on the prefix and described at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
   117  	switch prefix {
   118  	case elementSegmentPrefixLegacy:
   119  		// Legacy prefix which is WebAssembly 1.0 compatible.
   120  		expr, err := decodeConstantExpression(r, enabledFeatures)
   121  		if err != nil {
   122  			return nil, fmt.Errorf("read expr for offset: %w", err)
   123  		}
   124  
   125  		init, err := decodeElementInitValueVector(r)
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  
   130  		return &wasm.ElementSegment{
   131  			OffsetExpr: expr,
   132  			Init:       init,
   133  			Type:       wasm.RefTypeFuncref,
   134  			Mode:       wasm.ElementModeActive,
   135  			// Legacy prefix has the fixed table index zero.
   136  			TableIndex: 0,
   137  		}, nil
   138  	case elementSegmentPrefixPassiveFuncrefValueVector:
   139  		// Prefix 1 requires funcref.
   140  		if err = ensureElementKindFuncRef(r); err != nil {
   141  			return nil, err
   142  		}
   143  
   144  		init, err := decodeElementInitValueVector(r)
   145  		if err != nil {
   146  			return nil, err
   147  		}
   148  		return &wasm.ElementSegment{
   149  			Init: init,
   150  			Type: wasm.RefTypeFuncref,
   151  			Mode: wasm.ElementModePassive,
   152  		}, nil
   153  	case elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex:
   154  		tableIndex, _, err := leb128.DecodeUint32(r)
   155  		if err != nil {
   156  			return nil, fmt.Errorf("get size of vector: %w", err)
   157  		}
   158  
   159  		if tableIndex != 0 {
   160  			if err := enabledFeatures.Require(wasm.FeatureReferenceTypes); err != nil {
   161  				return nil, fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err)
   162  			}
   163  		}
   164  
   165  		expr, err := decodeConstantExpression(r, enabledFeatures)
   166  		if err != nil {
   167  			return nil, fmt.Errorf("read expr for offset: %w", err)
   168  		}
   169  
   170  		// Prefix 2 requires funcref.
   171  		if err = ensureElementKindFuncRef(r); err != nil {
   172  			return nil, err
   173  		}
   174  
   175  		init, err := decodeElementInitValueVector(r)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  		return &wasm.ElementSegment{
   180  			OffsetExpr: expr,
   181  			Init:       init,
   182  			Type:       wasm.RefTypeFuncref,
   183  			Mode:       wasm.ElementModeActive,
   184  			TableIndex: tableIndex,
   185  		}, nil
   186  	case elementSegmentPrefixDeclarativeFuncrefValueVector:
   187  		// Prefix 3 requires funcref.
   188  		if err = ensureElementKindFuncRef(r); err != nil {
   189  			return nil, err
   190  		}
   191  		init, err := decodeElementInitValueVector(r)
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		return &wasm.ElementSegment{
   196  			Init: init,
   197  			Type: wasm.RefTypeFuncref,
   198  			Mode: wasm.ElementModeDeclarative,
   199  		}, nil
   200  	case elementSegmentPrefixActiveFuncrefConstExprVector:
   201  		expr, err := decodeConstantExpression(r, enabledFeatures)
   202  		if err != nil {
   203  			return nil, fmt.Errorf("read expr for offset: %w", err)
   204  		}
   205  
   206  		init, err := decodeElementConstExprVector(r, wasm.RefTypeFuncref, enabledFeatures)
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  
   211  		return &wasm.ElementSegment{
   212  			OffsetExpr: expr,
   213  			Init:       init,
   214  			Type:       wasm.RefTypeFuncref,
   215  			Mode:       wasm.ElementModeActive,
   216  			TableIndex: 0,
   217  		}, nil
   218  	case elementSegmentPrefixPassiveConstExprVector:
   219  		refType, err := decodeElementRefType(r)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  		init, err := decodeElementConstExprVector(r, refType, enabledFeatures)
   224  		if err != nil {
   225  			return nil, err
   226  		}
   227  		return &wasm.ElementSegment{
   228  			Init: init,
   229  			Type: refType,
   230  			Mode: wasm.ElementModePassive,
   231  		}, nil
   232  	case elementSegmentPrefixActiveConstExprVector:
   233  		tableIndex, _, err := leb128.DecodeUint32(r)
   234  		if err != nil {
   235  			return nil, fmt.Errorf("get size of vector: %w", err)
   236  		}
   237  
   238  		if tableIndex != 0 {
   239  			if err := enabledFeatures.Require(wasm.FeatureReferenceTypes); err != nil {
   240  				return nil, fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err)
   241  			}
   242  		}
   243  		expr, err := decodeConstantExpression(r, enabledFeatures)
   244  		if err != nil {
   245  			return nil, fmt.Errorf("read expr for offset: %w", err)
   246  		}
   247  
   248  		refType, err := decodeElementRefType(r)
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  
   253  		init, err := decodeElementConstExprVector(r, refType, enabledFeatures)
   254  		if err != nil {
   255  			return nil, err
   256  		}
   257  
   258  		return &wasm.ElementSegment{
   259  			OffsetExpr: expr,
   260  			Init:       init,
   261  			Type:       refType,
   262  			Mode:       wasm.ElementModeActive,
   263  			TableIndex: tableIndex,
   264  		}, nil
   265  	case elementSegmentPrefixDeclarativeConstExprVector:
   266  		refType, err := decodeElementRefType(r)
   267  		if err != nil {
   268  			return nil, err
   269  		}
   270  		init, err := decodeElementConstExprVector(r, refType, enabledFeatures)
   271  		if err != nil {
   272  			return nil, err
   273  		}
   274  		return &wasm.ElementSegment{
   275  			Init: init,
   276  			Type: refType,
   277  			Mode: wasm.ElementModeDeclarative,
   278  		}, nil
   279  	default:
   280  		return nil, fmt.Errorf("invalid element segment prefix: 0x%x", prefix)
   281  	}
   282  }
   283  
   284  // encodeCode returns the wasm.ElementSegment encoded in WebAssembly 1.0 (20191205) Binary Format.
   285  //
   286  // https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#element-section%E2%91%A0
   287  func encodeElement(e *wasm.ElementSegment) (ret []byte) {
   288  	if e.Mode == wasm.ElementModeActive {
   289  		ret = append(ret, leb128.EncodeInt32(int32(e.TableIndex))...)
   290  		ret = append(ret, encodeConstantExpression(e.OffsetExpr)...)
   291  		ret = append(ret, leb128.EncodeUint32(uint32(len(e.Init)))...)
   292  		for _, idx := range e.Init {
   293  			ret = append(ret, leb128.EncodeInt32(int32(*idx))...)
   294  		}
   295  	} else {
   296  		panic("TODO: support encoding for non-active elements in bulk-memory-operations proposal")
   297  	}
   298  	return
   299  }