| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| // ignore_for_file: non_constant_identifier_names |
| |
| import '../ir/ir.dart' as ir; |
| import 'builder.dart'; |
| import '../../source_map.dart'; |
| |
| // TODO(joshualitt): Suggested further optimizations: |
| // 1) Add size estimates to `_Instruction`, and then remove logic where we |
| // need to serialize instructions to get their size. |
| // 2) Emit binary directly to a filestream, instead of buffering with a |
| // Uint8List. |
| |
| /// Thrown when Wasm bytecode validation fails. |
| class ValidationError { |
| final String trace; |
| final String error; |
| |
| ValidationError(this.trace, this.error); |
| |
| @override |
| String toString() => "$trace\n$error"; |
| } |
| |
| /// Label to use as target for branch instructions. |
| abstract class Label { |
| final List<ir.ValueType> inputs; |
| final List<ir.ValueType> outputs; |
| |
| late final int? ordinal; |
| late final int depth; |
| late final int baseStackHeight; |
| late final bool reachable; |
| late final int localInitializationStackHeight; |
| |
| Label._(this.inputs, this.outputs); |
| |
| List<ir.ValueType> get targetTypes; |
| |
| bool get hasOrdinal => ordinal != null; |
| |
| @override |
| String toString() => "L$ordinal"; |
| } |
| |
| class Expression extends Label { |
| Expression(super.inputs, super.outputs) : super._() { |
| ordinal = null; |
| depth = 0; |
| baseStackHeight = 0; |
| reachable = true; |
| localInitializationStackHeight = 0; |
| } |
| |
| @override |
| List<ir.ValueType> get targetTypes => outputs; |
| } |
| |
| class Block extends Label { |
| Block(super.inputs, super.outputs) : super._(); |
| |
| @override |
| List<ir.ValueType> get targetTypes => outputs; |
| } |
| |
| class Loop extends Label { |
| Loop(super.inputs, super.outputs) : super._(); |
| |
| @override |
| List<ir.ValueType> get targetTypes => inputs; |
| } |
| |
| class If extends Label { |
| bool hasElse = false; |
| |
| If(super.inputs, super.outputs) : super._(); |
| |
| @override |
| List<ir.ValueType> get targetTypes => outputs; |
| } |
| |
| class Try extends Label { |
| bool hasCatch = false; |
| |
| Try(super.inputs, super.outputs) : super._(); |
| |
| @override |
| List<ir.ValueType> get targetTypes => outputs; |
| } |
| |
| /// A sequence of Wasm instructions. |
| /// |
| /// Instructions can be added to the sequence by calling the corresponding |
| /// instruction methods. |
| /// |
| /// If asserts are enabled, the instruction methods will perform on-the-fly |
| /// validation and throw a [ValidationError] if validation fails. |
| class InstructionsBuilder with Builder<ir.Instructions> { |
| /// The module containing these instructions. |
| final ModuleBuilder module; |
| |
| /// Locals declared in this body, including parameters. |
| final List<ir.Local> locals = []; |
| |
| /// Whether a textual trace of the instruction stream should be recorded when |
| /// emitting instructions (provided asserts are enabled). |
| /// |
| /// This trace can be accessed via the [trace] property and will be part of |
| /// the exception text if a validation error occurs. |
| bool traceEnabled = true; |
| |
| /// Column width for the instructions. |
| int instructionColumnWidth = 50; |
| |
| /// The maximum number of stack slots for which to print the types after each |
| /// instruction. When the stack is higher than this, some elements in the |
| /// middle of the stack are left out. |
| int maxStackShown = 10; |
| |
| /// Mappings for the instructions in [_instructions] to their source code. |
| /// |
| /// Since we add mappings as we generate instructions, this will be sorted |
| /// based on [SourceMapping.instructionOffset]. |
| final List<SourceMapping>? _sourceMappings; |
| |
| int _indent = 1; |
| final List<String> _traceLines = []; |
| |
| int _labelCount = 0; |
| final List<Label> _labelStack = []; |
| final List<ir.ValueType> _stackTypes = []; |
| bool _reachable = true; |
| |
| /// Whether each local is currently definitely initialized. |
| final List<bool> _localInitialized = []; |
| |
| /// Stack of currently initialized non-defaultable locals. |
| final List<int> _localInitializationStack = []; |
| |
| /// List of instructions. |
| final List<ir.Instruction> _instructions = []; |
| |
| /// Stored stack traces leading to the instructions for watch points. |
| final Map<ir.Instruction, StackTrace>? _stackTraces; |
| |
| /// Create a new instruction sequence. |
| InstructionsBuilder( |
| this.module, List<ir.ValueType> inputs, List<ir.ValueType> outputs) |
| : _stackTraces = module.watchPoints.isNotEmpty ? {} : null, |
| _sourceMappings = module.sourceMapUrl == null ? null : [] { |
| _labelStack.add(Expression(const [], outputs)); |
| for (ir.ValueType paramType in inputs) { |
| _addParameter(paramType); |
| } |
| } |
| |
| /// Whether the instruction sequence has been completed by the final `end`. |
| bool get isComplete => _labelStack.isEmpty; |
| |
| /// Textual trace of the instructions. |
| String get trace => _traceLines.join(); |
| |
| bool get recordSourceMaps => _sourceMappings != null; |
| |
| @override |
| ir.Instructions forceBuild() => ir.Instructions( |
| locals, _instructions, _stackTraces, _traceLines, _sourceMappings); |
| |
| void _add(ir.Instruction i) { |
| if (!_reachable) return; |
| _instructions.add(i); |
| if (module.watchPoints.isNotEmpty) { |
| _stackTraces![i] = StackTrace.current; |
| } |
| } |
| |
| ir.Local _addParameter(ir.ValueType type) { |
| final local = ir.Local(locals.length, type); |
| locals.add(local); |
| _localInitialized.add(true); |
| return local; |
| } |
| |
| ir.Local addLocal(ir.ValueType type) { |
| final local = ir.Local(locals.length, type); |
| locals.add(local); |
| _localInitialized.add(type.defaultable); |
| return local; |
| } |
| |
| bool _initializeLocal(ir.Local local) { |
| if (!_localInitialized[local.index]) { |
| _localInitialized[local.index] = true; |
| _localInitializationStack.add(local.index); |
| } |
| return true; |
| } |
| |
| bool _localIsInitialized(ir.Local local) { |
| return _localInitialized[local.index]; |
| } |
| |
| void _resetLocalInitialization(Label label) { |
| while (_localInitializationStack.length > |
| label.localInitializationStackHeight) { |
| _localInitialized[_localInitializationStack.removeLast()] = false; |
| } |
| } |
| |
| bool _debugTrace(List<Object>? trace, |
| {required bool reachableAfter, |
| int indentBefore = 0, |
| int indentAfter = 0}) { |
| if (traceEnabled && trace != null) { |
| _indent += indentBefore; |
| String instr = "${" " * _indent} ${trace.join(" ")}"; |
| instr = instr.length > instructionColumnWidth - 2 |
| ? "${instr.substring(0, instructionColumnWidth - 4)}... " |
| : instr.padRight(instructionColumnWidth); |
| final int stackHeight = _stackTypes.length; |
| final String stack = reachableAfter |
| ? stackHeight <= maxStackShown |
| ? _stackTypes.join(', ') |
| : [ |
| ..._stackTypes.sublist(0, maxStackShown ~/ 2), |
| "... ${stackHeight - maxStackShown} omitted ...", |
| ..._stackTypes.sublist(stackHeight - (maxStackShown + 1) ~/ 2) |
| ].join(', ') |
| : "-"; |
| final String line = "$instr$stack\n"; |
| _indent += indentAfter; |
| |
| _traceLines.add(line); |
| } |
| return true; |
| } |
| |
| bool _comment(String text) { |
| if (traceEnabled) { |
| final String line = "${" " * _indent} ;; $text\n"; |
| _traceLines.add(line); |
| } |
| return true; |
| } |
| |
| Never _reportError(String error) { |
| throw ValidationError(trace, error); |
| } |
| |
| ir.ValueType get _topOfStack { |
| if (!_reachable) return ir.RefType.common(nullable: true); |
| if (_stackTypes.isEmpty) _reportError("Stack underflow"); |
| return _stackTypes.last; |
| } |
| |
| Label get _topOfLabelStack { |
| if (_labelStack.isEmpty) _reportError("Label stack underflow"); |
| return _labelStack.last; |
| } |
| |
| List<ir.ValueType> _stack(int n) { |
| if (_stackTypes.length < n) _reportError("Stack underflow"); |
| return _stackTypes.sublist(_stackTypes.length - n); |
| } |
| |
| List<ir.ValueType> get stack => _stackTypes; |
| |
| List<ir.ValueType> _checkStackTypes(List<ir.ValueType> inputs, |
| [List<ir.ValueType>? stack]) { |
| stack ??= _stack(inputs.length); |
| bool typesMatch = true; |
| for (int i = 0; i < inputs.length; i++) { |
| if (!stack[i].isSubtypeOf(inputs[i])) { |
| typesMatch = false; |
| break; |
| } |
| } |
| if (!typesMatch) { |
| final String expected = inputs.join(', '); |
| final String got = stack.join(', '); |
| _reportError("Expected [$expected], but stack contained [$got]"); |
| } |
| return stack; |
| } |
| |
| bool _verifyTypes(List<ir.ValueType> inputs, List<ir.ValueType> outputs, |
| {List<Object>? trace, bool reachableAfter = true}) { |
| return _verifyTypesFun(inputs, (_) => outputs, |
| trace: trace, reachableAfter: reachableAfter); |
| } |
| |
| bool _verifyTypesFun(List<ir.ValueType> inputs, |
| List<ir.ValueType> Function(List<ir.ValueType>) outputsFun, |
| {List<Object>? trace, bool reachableAfter = true}) { |
| if (!_reachable) { |
| return _debugTrace(trace, reachableAfter: false); |
| } |
| final int baseStackHeight = _topOfLabelStack.baseStackHeight; |
| if (_stackTypes.length - inputs.length < baseStackHeight) { |
| final String expected = inputs.join(', '); |
| final String got = _stackTypes.sublist(baseStackHeight).join(', '); |
| _reportError( |
| "Underflowing base stack of innermost block: expected [$expected], " |
| "but stack contained [$got]"); |
| } |
| final List<ir.ValueType> stack = _checkStackTypes(inputs); |
| _stackTypes.length -= inputs.length; |
| _stackTypes.addAll(outputsFun(stack)); |
| return _debugTrace(trace, reachableAfter: reachableAfter); |
| } |
| |
| bool _verifyBranchTypes(Label label, |
| [int popped = 0, List<ir.ValueType> pushed = const []]) { |
| if (!_reachable) { |
| return true; |
| } |
| final List<ir.ValueType> inputs = label.targetTypes; |
| if (_stackTypes.length - popped + pushed.length - inputs.length < |
| label.baseStackHeight) { |
| _reportError("Underflowing base stack of target label"); |
| } |
| final List<ir.ValueType> stack = inputs.length <= pushed.length |
| ? pushed.sublist(pushed.length - inputs.length) |
| : [ |
| ..._stackTypes.sublist( |
| _stackTypes.length - popped + pushed.length - inputs.length, |
| _stackTypes.length - popped), |
| ...pushed |
| ]; |
| _checkStackTypes(inputs, stack); |
| return true; |
| } |
| |
| bool _verifyStartOfBlock(Label label, {required List<Object> trace}) { |
| return _debugTrace( |
| ["$label:", ...trace, ir.FunctionType(label.inputs, label.outputs)], |
| reachableAfter: _reachable, indentAfter: 1); |
| } |
| |
| bool _verifyEndOfBlock(List<ir.ValueType> outputs, |
| {required List<Object> trace, |
| required bool reachableAfter, |
| required bool reindent}) { |
| final Label label = _topOfLabelStack; |
| if (_reachable) { |
| final int expectedHeight = label.baseStackHeight + label.outputs.length; |
| if (_stackTypes.length != expectedHeight) { |
| _reportError("Incorrect stack height at end of block" |
| " (expected $expectedHeight, actual ${_stackTypes.length})"); |
| } |
| _checkStackTypes(label.outputs); |
| } |
| if (label.reachable) { |
| assert(_stackTypes.length >= label.baseStackHeight); |
| _stackTypes.length = label.baseStackHeight; |
| _stackTypes.addAll(outputs); |
| } |
| _resetLocalInitialization(label); |
| return _debugTrace([if (label.hasOrdinal) "$label:", ...trace], |
| reachableAfter: reachableAfter, |
| indentBefore: -1, |
| indentAfter: reindent ? 1 : 0); |
| } |
| |
| // Source maps |
| |
| /// Start mapping added instructions to the source location given in |
| /// arguments. |
| /// |
| /// This assumes [recordSourceMaps] is `true`. |
| void startSourceMapping(Uri fileUri, int line, int col, String? name) { |
| _addSourceMapping( |
| SourceMapping(_instructions.length, fileUri, line, col, name)); |
| } |
| |
| /// Stop mapping added instructions to the last source location given in |
| /// [startSourceMapping]. |
| /// |
| /// The instructions added after this won't have a mapping in the source map. |
| /// |
| /// This assumes [recordSourceMaps] is `true`. |
| void stopSourceMapping() { |
| _addSourceMapping(SourceMapping.unmapped(_instructions.length)); |
| } |
| |
| void _addSourceMapping(SourceMapping mapping) { |
| final sourceMappings = _sourceMappings!; |
| |
| if (sourceMappings.isNotEmpty) { |
| final lastMapping = sourceMappings.last; |
| |
| // Check if we are overriding the current source location. This can |
| // happen when we restore the source location after a compiling a |
| // sub-tree, and the next node in the AST immediately updates the source |
| // location. The restored location is then never used. |
| if (lastMapping.instructionOffset == mapping.instructionOffset) { |
| sourceMappings.removeLast(); |
| sourceMappings.add(mapping); |
| return; |
| } |
| |
| // Check if we the new mapping maps to the same source as the old |
| // mapping. This happens when we have e.g. an instance field get like |
| // `length`, which gets transformed by the front-end as `this.length`. In |
| // this case `this` and `length` will have the same source location. |
| if (lastMapping.sourceInfo == mapping.sourceInfo) { |
| return; |
| } |
| } |
| |
| sourceMappings.add(mapping); |
| } |
| |
| // Meta |
| |
| /// Emit a comment. |
| void comment(String text) { |
| assert(_comment(text)); |
| } |
| |
| // Control instructions |
| |
| /// Emit an `unreachable` instruction. |
| void unreachable() { |
| assert(_verifyTypes(const [], const [], |
| trace: const ['unreachable'], reachableAfter: false)); |
| _add(const ir.Unreachable()); |
| _reachable = false; |
| } |
| |
| /// Emit a `nop` instruction. |
| void nop() { |
| assert(_verifyTypes(const [], const [], trace: const ['nop'])); |
| _add(const ir.Nop()); |
| } |
| |
| Label _pushLabel(Label label, {required List<Object> trace}) { |
| assert(_verifyTypes(label.inputs, label.inputs)); |
| label.ordinal = ++_labelCount; |
| label.depth = _labelStack.length; |
| label.baseStackHeight = _stackTypes.length - label.inputs.length; |
| label.reachable = _reachable; |
| label.localInitializationStackHeight = _localInitializationStack.length; |
| _labelStack.add(label); |
| assert(_verifyStartOfBlock(label, trace: trace)); |
| return label; |
| } |
| |
| Label _beginBlock( |
| Label label, |
| ir.Instruction Function() noEffect, |
| ir.Instruction Function(ir.ValueType type) oneOutput, |
| ir.Instruction Function(ir.FunctionType type) function) { |
| if (label.inputs.isEmpty && label.outputs.isEmpty) { |
| _add(noEffect()); |
| } else if (label.inputs.isEmpty && label.outputs.length == 1) { |
| _add(oneOutput(label.outputs.single)); |
| } else { |
| _add(function(module.types.defineFunction(label.inputs, label.outputs))); |
| } |
| return label; |
| } |
| |
| /// Emit a `block` instruction. |
| /// Branching to the returned label will branch to the matching `end`. |
| Label block( |
| [List<ir.ValueType> inputs = const [], |
| List<ir.ValueType> outputs = const []]) => |
| _beginBlock( |
| _pushLabel(Block(inputs, outputs), trace: const ['block']), |
| ir.BeginNoEffectBlock.new, |
| ir.BeginOneOutputBlock.new, |
| ir.BeginFunctionBlock.new); |
| |
| /// Emit a `loop` instruction. |
| /// Branching to the returned label will branch to the `loop`. |
| Label loop( |
| [List<ir.ValueType> inputs = const [], |
| List<ir.ValueType> outputs = const []]) => |
| _beginBlock( |
| _pushLabel(Loop(inputs, outputs), trace: const ['loop']), |
| ir.BeginNoEffectLoop.new, |
| ir.BeginOneOutputLoop.new, |
| ir.BeginFunctionLoop.new); |
| |
| /// Emit an `if` instruction. |
| /// Branching to the returned label will branch to the matching `end`. |
| Label if_( |
| [List<ir.ValueType> inputs = const [], |
| List<ir.ValueType> outputs = const []]) { |
| assert(_verifyTypes(const [ir.NumType.i32], const [])); |
| return _beginBlock( |
| _pushLabel(If(inputs, outputs), trace: const ['if']), |
| ir.BeginNoEffectIf.new, |
| ir.BeginOneOutputIf.new, |
| ir.BeginFunctionIf.new); |
| } |
| |
| /// Emit an `else` instruction. |
| void else_() { |
| assert(_topOfLabelStack is If || |
| _reportError("Unexpected 'else' (not in 'if' block)")); |
| final If label = _topOfLabelStack as If; |
| assert(!label.hasElse || _reportError("Duplicate 'else' in 'if' block")); |
| assert(_verifyEndOfBlock(label.inputs, |
| trace: const ['else'], |
| reachableAfter: _topOfLabelStack.reachable, |
| reindent: true)); |
| label.hasElse = true; |
| _reachable = _topOfLabelStack.reachable; |
| _add(const ir.Else()); |
| } |
| |
| /// Emit a `try` instruction. |
| Label try_( |
| [List<ir.ValueType> inputs = const [], |
| List<ir.ValueType> outputs = const []]) => |
| _beginBlock( |
| _pushLabel(Try(inputs, outputs), trace: const ['try']), |
| ir.BeginNoEffectTry.new, |
| ir.BeginOneOutputTry.new, |
| ir.BeginFunctionTry.new); |
| |
| /// Emit a `catch` instruction. |
| void catch_(ir.Tag tag) { |
| assert(_topOfLabelStack is Try || |
| _reportError("Unexpected 'catch' (not in 'try' block)")); |
| final Try try_ = _topOfLabelStack as Try; |
| assert(_verifyEndOfBlock(tag.type.inputs, |
| trace: ['catch', tag], reachableAfter: try_.reachable, reindent: true)); |
| try_.hasCatch = true; |
| _reachable = try_.reachable; |
| _add(ir.Catch(tag)); |
| } |
| |
| void catch_all() { |
| assert(_topOfLabelStack is Try || |
| _reportError("Unexpected 'catch_all' (not in 'try' block)")); |
| final Try try_ = _topOfLabelStack as Try; |
| assert(_verifyEndOfBlock([], |
| trace: ['catch_all'], reachableAfter: try_.reachable, reindent: true)); |
| try_.hasCatch = true; |
| _reachable = try_.reachable; |
| _add(const ir.CatchAll()); |
| } |
| |
| /// Emit a `throw` instruction. |
| void throw_(ir.Tag tag) { |
| assert(_verifyTypes(tag.type.inputs, const [], trace: ['throw', tag])); |
| _add(ir.Throw(tag)); |
| _reachable = false; |
| } |
| |
| /// Emit a `rethrow` instruction. |
| void rethrow_(Label label) { |
| assert(label is Try && label.hasCatch); |
| assert(_verifyTypes(const [], const [], trace: ['rethrow', label])); |
| _add(ir.Rethrow(_labelIndex(label))); |
| _reachable = false; |
| } |
| |
| /// Emit an `end` instruction. |
| void end() { |
| assert(_verifyEndOfBlock(_topOfLabelStack.outputs, |
| trace: const ['end'], |
| reachableAfter: _topOfLabelStack.reachable, |
| reindent: false)); |
| _reachable = _topOfLabelStack.reachable; |
| _labelStack.removeLast(); |
| _add(const ir.End()); |
| } |
| |
| int _labelIndex(Label label) { |
| final int index = _labelStack.length - label.depth - 1; |
| assert(_labelStack[label.depth] == label); |
| return index; |
| } |
| |
| /// Emit a `br` instruction. |
| void br(Label label) { |
| assert(_verifyTypes(const [], const [], |
| trace: ['br', label], reachableAfter: false)); |
| assert(_verifyBranchTypes(label)); |
| _add(ir.Br(_labelIndex(label))); |
| _reachable = false; |
| } |
| |
| /// Emit a `br_if` instruction. |
| void br_if(Label label) { |
| assert(_verifyTypes(const [ir.NumType.i32], const [], |
| trace: ['br_if', label])); |
| assert(_verifyBranchTypes(label)); |
| _add(ir.BrIf(_labelIndex(label))); |
| } |
| |
| /// Emit a `br_table` instruction. |
| void br_table(List<Label> labels, Label defaultLabel) { |
| assert(_verifyTypes(const [ir.NumType.i32], const [], |
| trace: ['br_table', ...labels, defaultLabel], reachableAfter: false)); |
| for (var label in labels) { |
| assert(_verifyBranchTypes(label)); |
| } |
| assert(_verifyBranchTypes(defaultLabel)); |
| _add(ir.BrTable( |
| labels.map(_labelIndex).toList(), _labelIndex(defaultLabel))); |
| _reachable = false; |
| } |
| |
| /// Emit a `return` instruction. |
| void return_() { |
| assert(_verifyTypes(_labelStack[0].outputs, const [], |
| trace: const ['return'], reachableAfter: false)); |
| _add(const ir.Return()); |
| _reachable = false; |
| } |
| |
| /// Emit a `call` instruction. |
| void call(ir.BaseFunction function) { |
| assert(_verifyTypes(function.type.inputs, function.type.outputs, |
| trace: ['call', function])); |
| _add(ir.Call(function)); |
| } |
| |
| /// Emit a `call_indirect` instruction. |
| void call_indirect(ir.FunctionType type, [ir.Table? table]) { |
| assert(_verifyTypes([...type.inputs, ir.NumType.i32], type.outputs, |
| trace: ['call_indirect', type, if (table != null) table.name])); |
| _add(ir.CallIndirect(type, table)); |
| } |
| |
| /// Emit a `call_ref` instruction. |
| void call_ref(ir.FunctionType type) { |
| assert(_verifyTypes( |
| [...type.inputs, ir.RefType.def(type, nullable: true)], type.outputs, |
| trace: ['call_ref', type])); |
| _add(ir.CallRef(type)); |
| } |
| |
| // Parametric instructions |
| |
| /// Emit a `drop` instruction. |
| void drop() { |
| assert(_verifyTypes([_topOfStack], const [], trace: const ['drop'])); |
| _add(const ir.Drop()); |
| } |
| |
| /// Emit a `select` instruction. |
| void select(ir.ValueType type) { |
| assert(_verifyTypes([type, type, ir.NumType.i32], [type], |
| trace: ['select', type])); |
| _add(ir.Select(type)); |
| } |
| |
| // Variable instructions |
| |
| /// Emit a `local.get` instruction. |
| void local_get(ir.Local local) { |
| assert(locals[local.index] == local); |
| assert(_verifyTypes(const [], [local.type], trace: ['local.get', local])); |
| assert(_localIsInitialized(local) || |
| _reportError("Uninitialized local with non-defaultable type")); |
| _add(ir.LocalGet(local)); |
| } |
| |
| /// Emit a `local.set` instruction. |
| void local_set(ir.Local local) { |
| assert(locals[local.index] == local); |
| assert(_verifyTypes([local.type], const [], trace: ['local.set', local])); |
| assert(_initializeLocal(local)); |
| _add(ir.LocalSet(local)); |
| } |
| |
| /// Emit a `local.tee` instruction. |
| void local_tee(ir.Local local) { |
| assert(locals[local.index] == local); |
| assert( |
| _verifyTypes([local.type], [local.type], trace: ['local.tee', local])); |
| assert(_initializeLocal(local)); |
| _add(ir.LocalTee(local)); |
| } |
| |
| /// Emit a `global.get` instruction. |
| void global_get(ir.Global global) { |
| assert(_verifyTypes(const [], [global.type.type], |
| trace: ['global.get', global])); |
| _add(ir.GlobalGet(global)); |
| } |
| |
| /// Emit a `global.set` instruction. |
| void global_set(ir.Global global) { |
| assert(global.type.mutable); |
| assert(_verifyTypes([global.type.type], const [], |
| trace: ['global.set', global])); |
| _add(ir.GlobalSet(global)); |
| } |
| |
| // Table instructions |
| |
| /// Emit a `table.get` instruction. |
| void table_get(ir.Table table) { |
| assert(_verifyTypes(const [ir.NumType.i32], [table.type], |
| trace: ['table.get', table.name])); |
| _add(ir.TableGet(table)); |
| } |
| |
| /// Emit a `table.set` instruction. |
| void table_set(ir.Table table) { |
| assert(_verifyTypes([ir.NumType.i32, table.type], const [], |
| trace: ['table.set', table.name])); |
| _add(ir.TableSet(table)); |
| } |
| |
| /// Emit a `table.size` instruction. |
| void table_size(ir.Table table) { |
| assert(_verifyTypes(const [], const [ir.NumType.i32], |
| trace: ['table.size', table.name])); |
| _add(ir.TableSize(table)); |
| } |
| |
| // Memory instructions |
| void _addMemoryInstruction( |
| ir.Instruction Function(ir.MemoryOffsetAlign memory) create, |
| ir.Memory memory, |
| {required int offset, |
| required int align}) => |
| _add(create(ir.MemoryOffsetAlign(memory, offset: offset, align: align))); |
| |
| /// Emit an `i32.load` instruction. |
| void i32_load(ir.Memory memory, int offset, [int align = 2]) { |
| assert(align >= 0 && align <= 2); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: ['i32.load', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I32Load.new, memory, offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.load` instruction. |
| void i64_load(ir.Memory memory, int offset, [int align = 3]) { |
| assert(align >= 0 && align <= 3); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i64], |
| trace: ['i64.load', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Load.new, memory, offset: offset, align: align); |
| } |
| |
| /// Emit an `f32.load` instruction. |
| void f32_load(ir.Memory memory, int offset, [int align = 2]) { |
| assert(align >= 0 && align <= 2); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.f32], |
| trace: ['f32.load', memory.name, offset, align])); |
| _addMemoryInstruction(ir.F32Load.new, memory, offset: offset, align: align); |
| } |
| |
| /// Emit an `f64.load` instruction. |
| void f64_load(ir.Memory memory, int offset, [int align = 3]) { |
| assert(align >= 0 && align <= 3); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.f64], |
| trace: ['f64.load', memory.name, offset, align])); |
| _addMemoryInstruction(ir.F64Load.new, memory, offset: offset, align: align); |
| } |
| |
| /// Emit an `i32.load8_s` instruction. |
| void i32_load8_s(ir.Memory memory, int offset, [int align = 0]) { |
| assert(align == 0); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: ['i32.load8_s', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I32Load8S.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i32.load8_u` instruction. |
| void i32_load8_u(ir.Memory memory, int offset, [int align = 0]) { |
| assert(align == 0); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: ['i32.load8_u', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I32Load8U.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i32.load16_s` instruction. |
| void i32_load16_s(ir.Memory memory, int offset, [int align = 1]) { |
| assert(align >= 0 && align <= 1); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: ['i32.load16_s', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I32Load16S.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i32.load16_u` instruction. |
| void i32_load16_u(ir.Memory memory, int offset, [int align = 1]) { |
| assert(align >= 0 && align <= 1); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: ['i32.load16_u', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I32Load16U.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.load8_s` instruction. |
| void i64_load8_s(ir.Memory memory, int offset, [int align = 0]) { |
| assert(align == 0); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i64], |
| trace: ['i64.load8_s', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Load8S.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.load8_u` instruction. |
| void i64_load8_u(ir.Memory memory, int offset, [int align = 0]) { |
| assert(align == 0); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i64], |
| trace: ['i64.load8_u', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Load8U.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.load16_s` instruction. |
| void i64_load16_s(ir.Memory memory, int offset, [int align = 1]) { |
| assert(align >= 0 && align <= 1); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i64], |
| trace: ['i64.load16_s', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Load16S.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.load16_u` instruction. |
| void i64_load16_u(ir.Memory memory, int offset, [int align = 1]) { |
| assert(align >= 0 && align <= 1); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i64], |
| trace: ['i64.load16_u', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Load16U.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.load32_s` instruction. |
| void i64_load32_s(ir.Memory memory, int offset, [int align = 2]) { |
| assert(align >= 0 && align <= 2); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i64], |
| trace: ['i64.load32_s', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Load32S.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.load32_u` instruction. |
| void i64_load32_u(ir.Memory memory, int offset, [int align = 2]) { |
| assert(align >= 0 && align <= 2); |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i64], |
| trace: ['i64.load32_u', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Load32U.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i32.store` instruction. |
| void i32_store(ir.Memory memory, int offset, [int align = 2]) { |
| assert(align >= 0 && align <= 2); |
| assert(_verifyTypes(const [ir.NumType.i32, ir.NumType.i32], const [], |
| trace: ['i32.store', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I32Store.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.store` instruction. |
| void i64_store(ir.Memory memory, int offset, [int align = 3]) { |
| assert(align >= 0 && align <= 3); |
| assert(_verifyTypes(const [ir.NumType.i32, ir.NumType.i64], const [], |
| trace: ['i64.store', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Store.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `f32.store` instruction. |
| void f32_store(ir.Memory memory, int offset, [int align = 2]) { |
| assert(align >= 0 && align <= 2); |
| assert(_verifyTypes(const [ir.NumType.i32, ir.NumType.f32], const [], |
| trace: ['f32.store', memory.name, offset, align])); |
| _addMemoryInstruction(ir.F32Store.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `f64.store` instruction. |
| void f64_store(ir.Memory memory, int offset, [int align = 3]) { |
| assert(align >= 0 && align <= 3); |
| assert(_verifyTypes(const [ir.NumType.i32, ir.NumType.f64], const [], |
| trace: ['f64.store', memory.name, offset, align])); |
| _addMemoryInstruction(ir.F64Store.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i32.store8` instruction. |
| void i32_store8(ir.Memory memory, int offset, [int align = 0]) { |
| assert(align == 0); |
| assert(_verifyTypes(const [ir.NumType.i32, ir.NumType.i32], const [], |
| trace: ['i32.store8', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I32Store8.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i32.store16` instruction. |
| void i32_store16(ir.Memory memory, int offset, [int align = 1]) { |
| assert(align >= 0 && align <= 1); |
| assert(_verifyTypes(const [ir.NumType.i32, ir.NumType.i32], const [], |
| trace: ['i32.store16', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I32Store16.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.store8` instruction. |
| void i64_store8(ir.Memory memory, int offset, [int align = 0]) { |
| assert(align == 0); |
| assert(_verifyTypes(const [ir.NumType.i32, ir.NumType.i64], const [], |
| trace: ['i64.store8', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Store8.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.store16` instruction. |
| void i64_store16(ir.Memory memory, int offset, [int align = 1]) { |
| assert(align >= 0 && align <= 1); |
| assert(_verifyTypes(const [ir.NumType.i32, ir.NumType.i64], const [], |
| trace: ['i64.store16', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Store16.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit an `i64.store32` instruction. |
| void i64_store32(ir.Memory memory, int offset, [int align = 2]) { |
| assert(align >= 0 && align <= 2); |
| assert(_verifyTypes(const [ir.NumType.i32, ir.NumType.i64], const [], |
| trace: ['i64.store32', memory.name, offset, align])); |
| _addMemoryInstruction(ir.I64Store32.new, memory, |
| offset: offset, align: align); |
| } |
| |
| /// Emit a `memory.size` instruction. |
| void memory_size(ir.Memory memory) { |
| assert(_verifyTypes(const [], const [ir.NumType.i32])); |
| _add(ir.MemorySize(memory)); |
| } |
| |
| /// Emit a `memory.grow` instruction. |
| void memory_grow(ir.Memory memory) { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32])); |
| _add(ir.MemoryGrow(memory)); |
| } |
| |
| // Reference instructions |
| |
| /// Emit a `ref.null` instruction. |
| void ref_null(ir.HeapType heapType) { |
| assert(_verifyTypes(const [], [ir.RefType(heapType, nullable: true)], |
| trace: ['ref.null', heapType])); |
| _add(ir.RefNull(heapType)); |
| } |
| |
| /// Emit a `ref.is_null` instruction. |
| void ref_is_null() { |
| assert(_verifyTypes( |
| const [ir.RefType.common(nullable: true)], const [ir.NumType.i32], |
| trace: const ['ref.is_null'])); |
| _add(const ir.RefIsNull()); |
| } |
| |
| /// Emit a `ref.func` instruction. |
| void ref_func(ir.BaseFunction function) { |
| assert(_verifyTypes( |
| const [], [ir.RefType.def(function.type, nullable: false)], |
| trace: ['ref.func', function])); |
| _add(ir.RefFunc(function)); |
| } |
| |
| /// Emit a `ref.as_non_null` instruction. |
| void ref_as_non_null() { |
| assert(_verifyTypes(const [ir.RefType.common(nullable: true)], |
| [_topOfStack.withNullability(false)], |
| trace: const ['ref.as_non_null'])); |
| _add(const ir.RefAsNonNull()); |
| } |
| |
| /// Emit a `br_on_null` instruction. |
| void br_on_null(Label label) { |
| assert(_verifyTypes(const [ir.RefType.common(nullable: true)], |
| [_topOfStack.withNullability(false)], |
| trace: ['br_on_null', label])); |
| assert(_verifyBranchTypes(label, 1)); |
| _add(ir.BrOnNull(_labelIndex(label))); |
| } |
| |
| /// Emit a `ref.eq` instruction. |
| void ref_eq() { |
| assert(_verifyTypes( |
| const [ir.RefType.eq(nullable: true), ir.RefType.eq(nullable: true)], |
| const [ir.NumType.i32], |
| trace: const ['ref.eq'])); |
| _add(const ir.RefEq()); |
| } |
| |
| /// Emit a `br_on_non_null` instruction. |
| void br_on_non_null(Label label) { |
| assert(_verifyBranchTypes(label, 1, [_topOfStack.withNullability(false)])); |
| assert(_verifyTypes(const [ir.RefType.common(nullable: true)], const [], |
| trace: ['br_on_non_null', label])); |
| _add(ir.BrOnNonNull(_labelIndex(label))); |
| } |
| |
| /// Emit a `struct.get` instruction. |
| void struct_get(ir.StructType structType, int fieldIndex) { |
| assert(structType.fields[fieldIndex].type is ir.ValueType); |
| assert(_verifyTypes([ir.RefType.def(structType, nullable: true)], |
| [structType.fields[fieldIndex].type.unpacked], |
| trace: ['struct.get', structType, fieldIndex])); |
| _add(ir.StructGet(structType, fieldIndex)); |
| } |
| |
| /// Emit a `struct.get_s` instruction. |
| void struct_get_s(ir.StructType structType, int fieldIndex) { |
| assert(structType.fields[fieldIndex].type is ir.PackedType); |
| assert(_verifyTypes([ir.RefType.def(structType, nullable: true)], |
| [structType.fields[fieldIndex].type.unpacked], |
| trace: ['struct.get_s', structType, fieldIndex])); |
| _add(ir.StructGetS(structType, fieldIndex)); |
| } |
| |
| /// Emit a `struct.get_u` instruction. |
| void struct_get_u(ir.StructType structType, int fieldIndex) { |
| assert(structType.fields[fieldIndex].type is ir.PackedType); |
| assert(_verifyTypes([ir.RefType.def(structType, nullable: true)], |
| [structType.fields[fieldIndex].type.unpacked], |
| trace: ['struct.get_u', structType, fieldIndex])); |
| _add(ir.StructGetU(structType, fieldIndex)); |
| } |
| |
| /// Emit a `struct.set` instruction. |
| void struct_set(ir.StructType structType, int fieldIndex) { |
| assert(_verifyTypes([ |
| ir.RefType.def(structType, nullable: true), |
| structType.fields[fieldIndex].type.unpacked |
| ], const [], trace: [ |
| 'struct.set', |
| structType, |
| fieldIndex |
| ])); |
| _add(ir.StructSet(structType, fieldIndex)); |
| } |
| |
| /// Emit a `struct.new` instruction. |
| void struct_new(ir.StructType structType) { |
| assert(_verifyTypes([...structType.fields.map((f) => f.type.unpacked)], |
| [ir.RefType.def(structType, nullable: false)], |
| trace: ['struct.new', structType])); |
| _add(ir.StructNew(structType)); |
| } |
| |
| /// Emit a `struct.new_default` instruction. |
| void struct_new_default(ir.StructType structType) { |
| assert(_verifyTypes(const [], [ir.RefType.def(structType, nullable: false)], |
| trace: ['struct.new_default', structType])); |
| _add(ir.StructNewDefault(structType)); |
| } |
| |
| /// Emit an `array.get` instruction. |
| void array_get(ir.ArrayType arrayType) { |
| assert(arrayType.elementType.type is ir.ValueType); |
| assert(_verifyTypes( |
| [ir.RefType.def(arrayType, nullable: true), ir.NumType.i32], |
| [arrayType.elementType.type.unpacked], |
| trace: ['array.get', arrayType])); |
| _add(ir.ArrayGet(arrayType)); |
| } |
| |
| /// Emit an `array.get_s` instruction. |
| void array_get_s(ir.ArrayType arrayType) { |
| assert(arrayType.elementType.type is ir.PackedType); |
| assert(_verifyTypes( |
| [ir.RefType.def(arrayType, nullable: true), ir.NumType.i32], |
| [arrayType.elementType.type.unpacked], |
| trace: ['array.get_s', arrayType])); |
| _add(ir.ArrayGetS(arrayType)); |
| } |
| |
| /// Emit an `array.get_u` instruction. |
| void array_get_u(ir.ArrayType arrayType) { |
| assert(arrayType.elementType.type is ir.PackedType); |
| assert(_verifyTypes( |
| [ir.RefType.def(arrayType, nullable: true), ir.NumType.i32], |
| [arrayType.elementType.type.unpacked], |
| trace: ['array.get_u', arrayType])); |
| _add(ir.ArrayGetU(arrayType)); |
| } |
| |
| /// Emit an `array.set` instruction. |
| void array_set(ir.ArrayType arrayType) { |
| assert(_verifyTypes([ |
| ir.RefType.def(arrayType, nullable: true), |
| ir.NumType.i32, |
| arrayType.elementType.type.unpacked |
| ], const [], trace: [ |
| 'array.set', |
| arrayType |
| ])); |
| _add(ir.ArraySet(arrayType)); |
| } |
| |
| /// Emit an `array.len` instruction. |
| void array_len() { |
| assert(_verifyTypes( |
| [ir.RefType.array(nullable: true)], const [ir.NumType.i32], |
| trace: ['array.len'])); |
| _add(const ir.ArrayLen()); |
| } |
| |
| /// Emit an `array.new_fixed` instruction. |
| void array_new_fixed(ir.ArrayType arrayType, int length) { |
| ir.ValueType elementType = arrayType.elementType.type.unpacked; |
| assert(_verifyTypes([...List.filled(length, elementType)], |
| [ir.RefType.def(arrayType, nullable: false)], |
| trace: ['array.new_fixed', arrayType, length])); |
| _add(ir.ArrayNewFixed(arrayType, length)); |
| } |
| |
| /// Emit an `array.new` instruction. |
| void array_new(ir.ArrayType arrayType) { |
| assert(_verifyTypes([arrayType.elementType.type.unpacked, ir.NumType.i32], |
| [ir.RefType.def(arrayType, nullable: false)], |
| trace: ['array.new', arrayType])); |
| _add(ir.ArrayNew(arrayType)); |
| } |
| |
| /// Emit an `array.new_default` instruction. |
| void array_new_default(ir.ArrayType arrayType) { |
| assert(_verifyTypes( |
| [ir.NumType.i32], [ir.RefType.def(arrayType, nullable: false)], |
| trace: ['array.new_default', arrayType])); |
| _add(ir.ArrayNewDefault(arrayType)); |
| } |
| |
| /// Emit an `array.new_data` instruction. |
| void array_new_data(ir.ArrayType arrayType, ir.BaseDataSegment data) { |
| assert(arrayType.elementType.type.isPrimitive); |
| assert(_verifyTypes([ir.NumType.i32, ir.NumType.i32], |
| [ir.RefType.def(arrayType, nullable: false)], |
| trace: ['array.new_data', arrayType, data.index])); |
| _add(ir.ArrayNewData(arrayType, data)); |
| } |
| |
| /// Emit an `array.copy` instruction. |
| void array_copy(ir.ArrayType destArrayType, ir.ArrayType sourceArrayType) { |
| assert(_verifyTypes([ |
| ir.RefType.def(destArrayType, nullable: true), // dest |
| ir.NumType.i32, // dest_offset |
| ir.RefType.def(sourceArrayType, nullable: true), // source |
| ir.NumType.i32, // source_offset |
| ir.NumType.i32 // size |
| ], [], trace: [ |
| 'array.copy', |
| destArrayType, |
| sourceArrayType |
| ])); |
| _add(ir.ArrayCopy( |
| destArrayType: destArrayType, sourceArrayType: sourceArrayType)); |
| } |
| |
| /// Emit an `array.fill` instruction. |
| void array_fill(ir.ArrayType arrayType) { |
| assert(_verifyTypes([ |
| ir.RefType.def(arrayType, nullable: true), |
| ir.NumType.i32, // offset |
| arrayType.elementType.type.unpacked, // fill value |
| ir.NumType.i32 // size |
| ], [], trace: [ |
| 'array.copy', |
| arrayType, |
| ])); |
| _add(ir.ArrayFill(arrayType)); |
| } |
| |
| /// Emit an `i31.new` instruction. |
| void i31_new() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32], const [ir.RefType.i31(nullable: false)], |
| trace: const ['i31.new'])); |
| _add(const ir.I31New()); |
| } |
| |
| /// Emit an `i31.get_s` instruction. |
| void i31_get_s() { |
| assert(_verifyTypes( |
| const [ir.RefType.i31(nullable: false)], const [ir.NumType.i32], |
| trace: const ['i31.get_s'])); |
| _add(const ir.I31GetS()); |
| } |
| |
| /// Emit an `i31.get_u` instruction. |
| void i31_get_u() { |
| assert(_verifyTypes( |
| const [ir.RefType.i31(nullable: false)], const [ir.NumType.i32], |
| trace: const ['i31.get_u'])); |
| _add(const ir.I31GetU()); |
| } |
| |
| bool _verifyCast( |
| ir.RefType inputType, ir.RefType targetType, ir.ValueType outputType, |
| {List<Object>? trace}) { |
| _verifyTypes([inputType], [outputType], trace: trace); |
| if (!targetType.isSubtypeOf(inputType)) { |
| _reportError("Target type '$targetType' not a subtype of " |
| "input type '$inputType' in cast"); |
| } |
| return true; |
| } |
| |
| /// Emit a `ref.test` instruction. |
| void ref_test(ir.RefType targetType) { |
| assert(_verifyCast(ir.RefType(targetType.heapType.topType, nullable: true), |
| targetType, ir.NumType.i32, trace: [ |
| 'ref.test', |
| if (targetType.nullable) 'null', |
| targetType.heapType |
| ])); |
| _add(ir.RefTest(targetType)); |
| } |
| |
| /// Emit a `ref.cast` instruction. |
| void ref_cast(ir.RefType targetType) { |
| assert(_verifyCast(ir.RefType(targetType.heapType.topType, nullable: true), |
| targetType, targetType, trace: [ |
| 'ref.cast', |
| if (targetType.nullable) 'null', |
| targetType.heapType |
| ])); |
| _add(ir.RefCast(targetType)); |
| } |
| |
| /// Emit a `br_on_cast` instruction. |
| void br_on_cast(Label label, ir.RefType inputType, ir.RefType targetType) { |
| assert(_verifyCast(inputType, targetType, |
| inputType.withNullability(inputType.nullable && !targetType.nullable), |
| trace: [ |
| 'br_on_cast', |
| label, |
| if (inputType.nullable) 'null', |
| inputType.heapType, |
| if (targetType.nullable) 'null', |
| targetType.heapType |
| ])); |
| assert(_verifyBranchTypes(label, 1, [targetType])); |
| _add(ir.BrOnCast(_labelIndex(label), inputType, targetType)); |
| } |
| |
| /// Emit a `br_on_cast_fail` instruction. |
| void br_on_cast_fail( |
| Label label, ir.RefType inputType, ir.RefType targetType) { |
| assert(_verifyCast(inputType, targetType, targetType, trace: [ |
| 'br_on_cast_fail', |
| label, |
| if (inputType.nullable) 'null', |
| inputType.heapType, |
| if (targetType.nullable) 'null', |
| targetType.heapType |
| ])); |
| assert(_verifyBranchTypes(label, 1, [ |
| inputType.withNullability(inputType.nullable && !targetType.nullable) |
| ])); |
| _add(ir.BrOnCastFail(_labelIndex(label), inputType, targetType)); |
| } |
| |
| /// Emit an `extern.internalize` instruction. |
| void extern_internalize() { |
| assert(_verifyTypesFun(const [ir.RefType.extern(nullable: true)], |
| (inputs) => [ir.RefType.any(nullable: inputs.single.nullable)], |
| trace: ['extern.internalize'])); |
| _add(const ir.ExternInternalize()); |
| } |
| |
| /// Emit an `extern.externalize` instruction. |
| void extern_externalize() { |
| assert(_verifyTypesFun(const [ir.RefType.any(nullable: true)], |
| (inputs) => [ir.RefType.extern(nullable: inputs.single.nullable)], |
| trace: ['extern.externalize'])); |
| _add(const ir.ExternExternalize()); |
| } |
| |
| // Numeric instructions |
| |
| /// Emit an `i32.const` instruction. |
| void i32_const(int value) { |
| assert(_verifyTypes(const [], const [ir.NumType.i32], |
| trace: ['i32.const', value])); |
| assert(-1 << 31 <= value && value < 1 << 31); |
| _add(ir.I32Const(value)); |
| } |
| |
| /// Emit an `i64.const` instruction. |
| void i64_const(int value) { |
| assert(_verifyTypes(const [], const [ir.NumType.i64], |
| trace: ['i64.const', value])); |
| _add(ir.I64Const(value)); |
| } |
| |
| /// Emit an `f32.const` instruction. |
| void f32_const(double value) { |
| assert(_verifyTypes(const [], const [ir.NumType.f32], |
| trace: ['f32.const', value])); |
| _add(ir.F32Const(value)); |
| } |
| |
| /// Emit an `f64.const` instruction. |
| void f64_const(double value) { |
| assert(_verifyTypes(const [], const [ir.NumType.f64], |
| trace: ['f64.const', value])); |
| _add(ir.F64Const(value)); |
| } |
| |
| /// Emit an `i32.eqz` instruction. |
| void i32_eqz() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.eqz'])); |
| _add(const ir.I32Eqz()); |
| } |
| |
| /// Emit an `i32.eq` instruction. |
| void i32_eq() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.eq'])); |
| _add(const ir.I32Eq()); |
| } |
| |
| /// Emit an `i32.ne` instruction. |
| void i32_ne() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.ne'])); |
| _add(const ir.I32Ne()); |
| } |
| |
| /// Emit an `i32.lt_s` instruction. |
| void i32_lt_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.lt_s'])); |
| _add(const ir.I32LtS()); |
| } |
| |
| /// Emit an `i32.lt_u` instruction. |
| void i32_lt_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.lt_u'])); |
| _add(const ir.I32LtU()); |
| } |
| |
| /// Emit an `i32.gt_s` instruction. |
| void i32_gt_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.gt_s'])); |
| _add(const ir.I32GtS()); |
| } |
| |
| /// Emit an `i32.gt_u` instruction. |
| void i32_gt_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.gt_u'])); |
| _add(const ir.I32GtU()); |
| } |
| |
| /// Emit an `i32.le_s` instruction. |
| void i32_le_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.le_s'])); |
| _add(const ir.I32LeS()); |
| } |
| |
| /// Emit an `i32.le_u` instruction. |
| void i32_le_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.le_u'])); |
| _add(const ir.I32LeU()); |
| } |
| |
| /// Emit an `i32.ge_s` instruction. |
| void i32_ge_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.ge_s'])); |
| _add(const ir.I32GeS()); |
| } |
| |
| /// Emit an `i32.ge_u` instruction. |
| void i32_ge_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.ge_u'])); |
| _add(const ir.I32GeU()); |
| } |
| |
| /// Emit an `i64.eqz` instruction. |
| void i64_eqz() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.eqz'])); |
| _add(const ir.I64Eqz()); |
| } |
| |
| /// Emit an `i64.eq` instruction. |
| void i64_eq() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.eq'])); |
| _add(const ir.I64Eq()); |
| } |
| |
| /// Emit an `i64.ne` instruction. |
| void i64_ne() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.ne'])); |
| _add(const ir.I64Ne()); |
| } |
| |
| /// Emit an `i64.lt_s` instruction. |
| void i64_lt_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.lt_s'])); |
| _add(const ir.I64LtS()); |
| } |
| |
| /// Emit an `i64.lt_u` instruction. |
| void i64_lt_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.lt_u'])); |
| _add(const ir.I64LtU()); |
| } |
| |
| /// Emit an `i64.gt_s` instruction. |
| void i64_gt_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.gt_s'])); |
| _add(const ir.I64GtS()); |
| } |
| |
| /// Emit an `i64.gt_u` instruction. |
| void i64_gt_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.gt_u'])); |
| _add(const ir.I64GtU()); |
| } |
| |
| /// Emit an `i64.le_s` instruction. |
| void i64_le_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.le_s'])); |
| _add(const ir.I64LeS()); |
| } |
| |
| /// Emit an `i64.le_u` instruction. |
| void i64_le_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.le_u'])); |
| _add(const ir.I64LeU()); |
| } |
| |
| /// Emit an `i64.ge_s` instruction. |
| void i64_ge_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.ge_s'])); |
| _add(const ir.I64GeS()); |
| } |
| |
| /// Emit an `i64.ge_u` instruction. |
| void i64_ge_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i64.ge_u'])); |
| _add(const ir.I64GeU()); |
| } |
| |
| /// Emit an `f32.eq` instruction. |
| void f32_eq() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['f32.eq'])); |
| _add(const ir.F32Eq()); |
| } |
| |
| /// Emit an `f32.ne` instruction. |
| void f32_ne() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['f32.ne'])); |
| _add(const ir.F32Ne()); |
| } |
| |
| /// Emit an `f32.lt` instruction. |
| void f32_lt() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['f32.lt'])); |
| _add(const ir.F32Lt()); |
| } |
| |
| /// Emit an `f32.gt` instruction. |
| void f32_gt() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['f32.gt'])); |
| _add(const ir.F32Gt()); |
| } |
| |
| /// Emit an `f32.le` instruction. |
| void f32_le() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['f32.le'])); |
| _add(const ir.F32Le()); |
| } |
| |
| /// Emit an `f32.ge` instruction. |
| void f32_ge() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['f32.ge'])); |
| _add(const ir.F32Ge()); |
| } |
| |
| /// Emit an `f64.eq` instruction. |
| void f64_eq() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['f64.eq'])); |
| _add(const ir.F64Eq()); |
| } |
| |
| /// Emit an `f64.ne` instruction. |
| void f64_ne() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['f64.ne'])); |
| _add(const ir.F64Ne()); |
| } |
| |
| /// Emit an `f64.lt` instruction. |
| void f64_lt() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['f64.lt'])); |
| _add(const ir.F64Lt()); |
| } |
| |
| /// Emit an `f64.gt` instruction. |
| void f64_gt() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['f64.gt'])); |
| _add(const ir.F64Gt()); |
| } |
| |
| /// Emit an `f64.le` instruction. |
| void f64_le() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['f64.le'])); |
| _add(const ir.F64Le()); |
| } |
| |
| /// Emit an `f64.ge` instruction. |
| void f64_ge() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['f64.ge'])); |
| _add(const ir.F64Ge()); |
| } |
| |
| /// Emit an `i32.clz` instruction. |
| void i32_clz() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.clz'])); |
| _add(const ir.I32Clz()); |
| } |
| |
| /// Emit an `i32.ctz` instruction. |
| void i32_ctz() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.ctz'])); |
| _add(const ir.I32Ctz()); |
| } |
| |
| /// Emit an `i32.popcnt` instruction. |
| void i32_popcnt() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.popcnt'])); |
| _add(const ir.I32Popcnt()); |
| } |
| |
| /// Emit an `i32.add` instruction. |
| void i32_add() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.add'])); |
| _add(const ir.I32Add()); |
| } |
| |
| /// Emit an `i32.sub` instruction. |
| void i32_sub() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.sub'])); |
| _add(const ir.I32Sub()); |
| } |
| |
| /// Emit an `i32.mul` instruction. |
| void i32_mul() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.mul'])); |
| _add(const ir.I32Mul()); |
| } |
| |
| /// Emit an `i32.div_s` instruction. |
| void i32_div_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.div_s'])); |
| _add(const ir.I32DivS()); |
| } |
| |
| /// Emit an `i32.div_u` instruction. |
| void i32_div_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.div_u'])); |
| _add(const ir.I32DivU()); |
| } |
| |
| /// Emit an `i32.rem_s` instruction. |
| void i32_rem_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.rem_s'])); |
| _add(const ir.I32RemS()); |
| } |
| |
| /// Emit an `i32.rem_u` instruction. |
| void i32_rem_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.rem_u'])); |
| _add(const ir.I32RemU()); |
| } |
| |
| /// Emit an `i32.and` instruction. |
| void i32_and() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.and'])); |
| _add(const ir.I32And()); |
| } |
| |
| /// Emit an `i32.or` instruction. |
| void i32_or() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.or'])); |
| _add(const ir.I32Or()); |
| } |
| |
| /// Emit an `i32.xor` instruction. |
| void i32_xor() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.xor'])); |
| _add(const ir.I32Xor()); |
| } |
| |
| /// Emit an `i32.shl` instruction. |
| void i32_shl() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.shl'])); |
| _add(const ir.I32Shl()); |
| } |
| |
| /// Emit an `i32.shr_s` instruction. |
| void i32_shr_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.shr_s'])); |
| _add(const ir.I32ShrS()); |
| } |
| |
| /// Emit an `i32.shr_u` instruction. |
| void i32_shr_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.shr_u'])); |
| _add(const ir.I32ShrU()); |
| } |
| |
| /// Emit an `i32.rotl` instruction. |
| void i32_rotl() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.rotl'])); |
| _add(const ir.I32Rotl()); |
| } |
| |
| /// Emit an `i32.rotr` instruction. |
| void i32_rotr() { |
| assert(_verifyTypes( |
| const [ir.NumType.i32, ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.rotr'])); |
| _add(const ir.I32Rotr()); |
| } |
| |
| /// Emit an `i64.clz` instruction. |
| void i64_clz() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.clz'])); |
| _add(const ir.I64Clz()); |
| } |
| |
| /// Emit an `i64.ctz` instruction. |
| void i64_ctz() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.ctz'])); |
| _add(const ir.I64Ctz()); |
| } |
| |
| /// Emit an `i64.popcnt` instruction. |
| void i64_popcnt() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.popcnt'])); |
| _add(const ir.I64Popcnt()); |
| } |
| |
| /// Emit an `i64.add` instruction. |
| void i64_add() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.add'])); |
| _add(const ir.I64Add()); |
| } |
| |
| /// Emit an `i64.sub` instruction. |
| void i64_sub() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.sub'])); |
| _add(const ir.I64Sub()); |
| } |
| |
| /// Emit an `i64.mul` instruction. |
| void i64_mul() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.mul'])); |
| _add(const ir.I64Mul()); |
| } |
| |
| /// Emit an `i64.div_s` instruction. |
| void i64_div_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.div_s'])); |
| _add(const ir.I64DivS()); |
| } |
| |
| /// Emit an `i64.div_u` instruction. |
| void i64_div_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.div_u'])); |
| _add(const ir.I64DivU()); |
| } |
| |
| /// Emit an `i64.rem_s` instruction. |
| void i64_rem_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.rem_s'])); |
| _add(const ir.I64RemS()); |
| } |
| |
| /// Emit an `i64.rem_u` instruction. |
| void i64_rem_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.rem_u'])); |
| _add(const ir.I64RemU()); |
| } |
| |
| /// Emit an `i64.and` instruction. |
| void i64_and() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.and'])); |
| _add(const ir.I64And()); |
| } |
| |
| /// Emit an `i64.or` instruction. |
| void i64_or() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.or'])); |
| _add(const ir.I64Or()); |
| } |
| |
| /// Emit an `i64.xor` instruction. |
| void i64_xor() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.xor'])); |
| _add(const ir.I64Xor()); |
| } |
| |
| /// Emit an `i64.shl` instruction. |
| void i64_shl() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.shl'])); |
| _add(const ir.I64Shl()); |
| } |
| |
| /// Emit an `i64.shr_s` instruction. |
| void i64_shr_s() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.shr_s'])); |
| _add(const ir.I64ShrS()); |
| } |
| |
| /// Emit an `i64.shr_u` instruction. |
| void i64_shr_u() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.shr_u'])); |
| _add(const ir.I64ShrU()); |
| } |
| |
| /// Emit an `i64.rotl` instruction. |
| void i64_rotl() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.rotl'])); |
| _add(const ir.I64Rotl()); |
| } |
| |
| /// Emit an `i64.rotr` instruction. |
| void i64_rotr() { |
| assert(_verifyTypes( |
| const [ir.NumType.i64, ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.rotr'])); |
| _add(const ir.I64Rotr()); |
| } |
| |
| /// Emit an `f32.abs` instruction. |
| void f32_abs() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.abs'])); |
| _add(const ir.F32Abs()); |
| } |
| |
| /// Emit an `f32.neg` instruction. |
| void f32_neg() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.neg'])); |
| _add(const ir.F32Neg()); |
| } |
| |
| /// Emit an `f32.ceil` instruction. |
| void f32_ceil() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.ceil'])); |
| _add(const ir.F32Ceil()); |
| } |
| |
| /// Emit an `f32.floor` instruction. |
| void f32_floor() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.floor'])); |
| _add(const ir.F32Floor()); |
| } |
| |
| /// Emit an `f32.trunc` instruction. |
| void f32_trunc() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.trunc'])); |
| _add(const ir.F32Trunc()); |
| } |
| |
| /// Emit an `f32.nearest` instruction. |
| void f32_nearest() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.nearest'])); |
| _add(const ir.F32Nearest()); |
| } |
| |
| /// Emit an `f32.sqrt` instruction. |
| void f32_sqrt() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.sqrt'])); |
| _add(const ir.F32Sqrt()); |
| } |
| |
| /// Emit an `f32.add` instruction. |
| void f32_add() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.add'])); |
| _add(const ir.F32Add()); |
| } |
| |
| /// Emit an `f32.sub` instruction. |
| void f32_sub() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.sub'])); |
| _add(const ir.F32Sub()); |
| } |
| |
| /// Emit an `f32.mul` instruction. |
| void f32_mul() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.mul'])); |
| _add(const ir.F32Mul()); |
| } |
| |
| /// Emit an `f32.div` instruction. |
| void f32_div() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.div'])); |
| _add(const ir.F32Div()); |
| } |
| |
| /// Emit an `f32.min` instruction. |
| void f32_min() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.min'])); |
| _add(const ir.F32Min()); |
| } |
| |
| /// Emit an `f32.max` instruction. |
| void f32_max() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.max'])); |
| _add(const ir.F32Max()); |
| } |
| |
| /// Emit an `f32.copysign` instruction. |
| void f32_copysign() { |
| assert(_verifyTypes( |
| const [ir.NumType.f32, ir.NumType.f32], const [ir.NumType.f32], |
| trace: const ['f32.copysign'])); |
| _add(const ir.F32Copysign()); |
| } |
| |
| /// Emit an `f64.abs` instruction. |
| void f64_abs() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.abs'])); |
| _add(const ir.F64Abs()); |
| } |
| |
| /// Emit an `f64.neg` instruction. |
| void f64_neg() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.neg'])); |
| _add(const ir.F64Neg()); |
| } |
| |
| /// Emit an `f64.ceil` instruction. |
| void f64_ceil() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.ceil'])); |
| _add(const ir.F64Ceil()); |
| } |
| |
| /// Emit an `f64.floor` instruction. |
| void f64_floor() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.floor'])); |
| _add(const ir.F64Floor()); |
| } |
| |
| /// Emit an `f64.trunc` instruction. |
| void f64_trunc() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.trunc'])); |
| _add(const ir.F64Trunc()); |
| } |
| |
| /// Emit an `f64.nearest` instruction. |
| void f64_nearest() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.nearest'])); |
| _add(const ir.F64Nearest()); |
| } |
| |
| /// Emit an `f64.sqrt` instruction. |
| void f64_sqrt() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.sqrt'])); |
| _add(const ir.F64Sqrt()); |
| } |
| |
| /// Emit an `f64.add` instruction. |
| void f64_add() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.add'])); |
| _add(const ir.F64Add()); |
| } |
| |
| /// Emit an `f64.sub` instruction. |
| void f64_sub() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.sub'])); |
| _add(const ir.F64Sub()); |
| } |
| |
| /// Emit an `f64.mul` instruction. |
| void f64_mul() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.mul'])); |
| _add(const ir.F64Mul()); |
| } |
| |
| /// Emit an `f64.div` instruction. |
| void f64_div() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.div'])); |
| _add(const ir.F64Div()); |
| } |
| |
| /// Emit an `f64.min` instruction. |
| void f64_min() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.min'])); |
| _add(const ir.F64Min()); |
| } |
| |
| /// Emit an `f64.max` instruction. |
| void f64_max() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.max'])); |
| _add(const ir.F64Max()); |
| } |
| |
| /// Emit an `f64.copysign` instruction. |
| void f64_copysign() { |
| assert(_verifyTypes( |
| const [ir.NumType.f64, ir.NumType.f64], const [ir.NumType.f64], |
| trace: const ['f64.copysign'])); |
| _add(const ir.F64Copysign()); |
| } |
| |
| /// Emit an `i32.wrap_i64` instruction. |
| void i32_wrap_i64() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.i32], |
| trace: const ['i32.wrap_i64'])); |
| _add(const ir.I32WrapI64()); |
| } |
| |
| /// Emit an `i32.trunc_f32_s` instruction. |
| void i32_trunc_f32_s() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['i32.trunc_f32_s'])); |
| _add(const ir.I32TruncF32S()); |
| } |
| |
| /// Emit an `i32.trunc_f32_u` instruction. |
| void i32_trunc_f32_u() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['i32.trunc_f32_u'])); |
| _add(const ir.I32TruncF32U()); |
| } |
| |
| /// Emit an `i32.trunc_f64_s` instruction. |
| void i32_trunc_f64_s() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['i32.trunc_f64_s'])); |
| _add(const ir.I32TruncF64S()); |
| } |
| |
| /// Emit an `i32.trunc_f64_u` instruction. |
| void i32_trunc_f64_u() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['i32.trunc_f64_u'])); |
| _add(const ir.I32TruncF64U()); |
| } |
| |
| /// Emit an `i64.extend_i32_s` instruction. |
| void i64_extend_i32_s() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i64], |
| trace: const ['i64.extend_i32_s'])); |
| _add(const ir.I64ExtendI32S()); |
| } |
| |
| /// Emit an `i64.extend_i32_u` instruction. |
| void i64_extend_i32_u() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i64], |
| trace: const ['i64.extend_i32_u'])); |
| _add(const ir.I64ExtendI32U()); |
| } |
| |
| /// Emit an `i64.trunc_f32_s` instruction. |
| void i64_trunc_f32_s() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.i64], |
| trace: const ['i64.trunc_f32_s'])); |
| _add(const ir.I64TruncF32S()); |
| } |
| |
| /// Emit an `i64.trunc_f32_u` instruction. |
| void i64_trunc_f32_u() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.i64], |
| trace: const ['i64.trunc_f32_u'])); |
| _add(const ir.I64TruncF32U()); |
| } |
| |
| /// Emit an `i64.trunc_f64_s` instruction. |
| void i64_trunc_f64_s() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.i64], |
| trace: const ['i64.trunc_f64_s'])); |
| _add(const ir.I64TruncF64S()); |
| } |
| |
| /// Emit an `i64.trunc_f64_u` instruction. |
| void i64_trunc_f64_u() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.i64], |
| trace: const ['i64.trunc_f64_u'])); |
| _add(const ir.I64TruncF64U()); |
| } |
| |
| /// Emit an `f32.convert_i32_s` instruction. |
| void f32_convert_i32_s() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.f32], |
| trace: const ['f32.convert_i32_s'])); |
| _add(const ir.F32ConvertI32S()); |
| } |
| |
| /// Emit an `f32.convert_i32_u` instruction. |
| void f32_convert_i32_u() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.f32], |
| trace: const ['f32.convert_i32_u'])); |
| _add(const ir.F32ConvertI32U()); |
| } |
| |
| /// Emit an `f32.convert_i64_s` instruction. |
| void f32_convert_i64_s() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.f32], |
| trace: const ['f32.convert_i64_s'])); |
| _add(const ir.F32ConvertI64S()); |
| } |
| |
| /// Emit an `f32.convert_i64_u` instruction. |
| void f32_convert_i64_u() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.f32], |
| trace: const ['f32.convert_i64_u'])); |
| _add(const ir.F32ConvertI64U()); |
| } |
| |
| /// Emit an `f32.demote_f64` instruction. |
| void f32_demote_f64() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.f32], |
| trace: const ['f32.demote_f64'])); |
| _add(const ir.F32DemoteF64()); |
| } |
| |
| /// Emit an `f64.convert_i32_s` instruction. |
| void f64_convert_i32_s() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.f64], |
| trace: const ['f64.convert_i32_s'])); |
| _add(const ir.F64ConvertI32S()); |
| } |
| |
| /// Emit an `f64.convert_i32_u` instruction. |
| void f64_convert_i32_u() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.f64], |
| trace: const ['f64.convert_i32_u'])); |
| _add(const ir.F64ConvertI32U()); |
| } |
| |
| /// Emit an `f64.convert_i64_s` instruction. |
| void f64_convert_i64_s() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.f64], |
| trace: const ['f64.convert_i64_s'])); |
| _add(const ir.F64ConvertI64S()); |
| } |
| |
| /// Emit an `f64.convert_i64_u` instruction. |
| void f64_convert_i64_u() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.f64], |
| trace: const ['f64.convert_i64_u'])); |
| _add(const ir.F64ConvertI64U()); |
| } |
| |
| /// Emit an `f64.promote_f32` instruction. |
| void f64_promote_f32() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.f64], |
| trace: const ['f64.promote_f32'])); |
| _add(const ir.F64PromoteF32()); |
| } |
| |
| /// Emit an `i32.reinterpret_f32` instruction. |
| void i32_reinterpret_f32() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['i32.reinterpret_f32'])); |
| _add(const ir.I32ReinterpretF32()); |
| } |
| |
| /// Emit an `i64.reinterpret_f64` instruction. |
| void i64_reinterpret_f64() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.i64], |
| trace: const ['i64.reinterpret_f64'])); |
| _add(const ir.I64ReinterpretF64()); |
| } |
| |
| /// Emit an `f32.reinterpret_i32` instruction. |
| void f32_reinterpret_i32() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.f32], |
| trace: const ['f32.reinterpret_i32'])); |
| _add(const ir.F32ReinterpretI32()); |
| } |
| |
| /// Emit an `f64.reinterpret_i64` instruction. |
| void f64_reinterpret_i64() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.f64], |
| trace: const ['f64.reinterpret_i64'])); |
| _add(const ir.F64ReinterpretI64()); |
| } |
| |
| /// Emit an `i32.extend8_s` instruction. |
| void i32_extend8_s() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.extend8_s'])); |
| _add(const ir.I32Extend8S()); |
| } |
| |
| /// Emit an `i32.extend16_s` instruction. |
| void i32_extend16_s() { |
| assert(_verifyTypes(const [ir.NumType.i32], const [ir.NumType.i32], |
| trace: const ['i32.extend16_s'])); |
| _add(const ir.I32Extend16S()); |
| } |
| |
| /// Emit an `i64.extend8_s` instruction. |
| void i64_extend8_s() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.extend8_s'])); |
| _add(const ir.I64Extend8S()); |
| } |
| |
| /// Emit an `i64.extend16_s` instruction. |
| void i64_extend16_s() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.extend16_s'])); |
| _add(const ir.I64Extend16S()); |
| } |
| |
| /// Emit an `i64.extend32_s` instruction. |
| void i64_extend32_s() { |
| assert(_verifyTypes(const [ir.NumType.i64], const [ir.NumType.i64], |
| trace: const ['i64.extend32_s'])); |
| _add(const ir.I64Extend32S()); |
| } |
| |
| /// Emit an `i32.trunc_sat_f32_s` instruction. |
| void i32_trunc_sat_f32_s() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['i32.trunc_sat_f32_s'])); |
| _add(const ir.I32TruncSatF32S()); |
| } |
| |
| /// Emit an `i32.trunc_sat_f32_u` instruction. |
| void i32_trunc_sat_f32_u() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.i32], |
| trace: const ['i32.trunc_sat_f32_u'])); |
| _add(const ir.I32TruncSatF32U()); |
| } |
| |
| /// Emit an `i32.trunc_sat_f64_s` instruction. |
| void i32_trunc_sat_f64_s() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['i32.trunc_sat_f64_s'])); |
| _add(const ir.I32TruncSatF64S()); |
| } |
| |
| /// Emit an `i32.trunc_sat_f64_u` instruction. |
| void i32_trunc_sat_f64_u() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.i32], |
| trace: const ['i32.trunc_sat_f64_u'])); |
| _add(const ir.I32TruncSatF64U()); |
| } |
| |
| /// Emit an `i64.trunc_sat_f32_s` instruction. |
| void i64_trunc_sat_f32_s() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.i64], |
| trace: const ['i64.trunc_sat_f32_s'])); |
| _add(const ir.I64TruncSatF32S()); |
| } |
| |
| /// Emit an `i64.trunc_sat_f32_u` instruction. |
| void i64_trunc_sat_f32_u() { |
| assert(_verifyTypes(const [ir.NumType.f32], const [ir.NumType.i64], |
| trace: const ['i64.trunc_sat_f32_u'])); |
| _add(const ir.I64TruncSatF32U()); |
| } |
| |
| /// Emit an `i64.trunc_sat_f64_s` instruction. |
| void i64_trunc_sat_f64_s() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.i64], |
| trace: const ['i64.trunc_sat_f64_s'])); |
| _add(const ir.I64TruncSatF64S()); |
| } |
| |
| /// Emit an `i64.trunc_sat_f64_u` instruction. |
| void i64_trunc_sat_f64_u() { |
| assert(_verifyTypes(const [ir.NumType.f64], const [ir.NumType.i64], |
| trace: const ['i64.trunc_sat_f64_u'])); |
| _add(const ir.I64TruncSatF64U()); |
| } |
| } |