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 }