wa-lang.org/wazero@v1.0.2/internal/wasm/binary/element.go (about)

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