| // 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. |
| |
| import 'dart:typed_data'; |
| |
| import '../../debug_info.dart'; |
| import '../ir/ir.dart' as ir; |
| import '../serialize/sections.dart'; |
| import 'builder.dart'; |
| |
| /// A Wasm module builder. |
| /// |
| /// NOTE: The [ModuleBuilder] contains builders for various constituents and |
| /// those in return will refer back to the [ModuleBuilder], making the builders |
| /// be cyclic data structures. |
| /// |
| /// The main reason for this is that e.g. an [InstructionsBuilder] may insert |
| /// new instructions. Doing so may require e.g. defining new function types. So |
| /// it does so by calling `moduleBuilder.functions.defineFunction()`. |
| /// |
| /// We could avoid some of the cyclic dependencies by passing the individual |
| /// builders down to other builders that need it, instead of passing an |
| /// [ModuleBuilder] down that contains all builders. |
| class ModuleBuilder with Builder<ir.Module> { |
| final ir.Module module = ir.Module.uninitialized(); |
| |
| final String moduleName; |
| final Uri? sourceMapUrl; |
| final List<int> watchPoints; |
| final DebugInfoTables? debugInfoTables; |
| late final TypesBuilder types; |
| late final functions = FunctionsBuilder(this); |
| late final elements = ElementsBuilder(this); |
| late final tables = TablesBuilder(module); |
| late final memories = MemoriesBuilder(module); |
| late final tags = TagsBuilder(module); |
| final dataSegments = DataSegmentsBuilder(); |
| late final globals = GlobalsBuilder(this); |
| final exports = ExportsBuilder(); |
| FunctionBuilder? _startFunction; |
| final List<ExtraCustomSection> _extraCustomSections = []; |
| |
| /// Whether relaxed SIMD instructions may be emitted into this module. |
| /// |
| /// Relaxed SIMD is a Wasm proposal separate from fixed-width SIMD, and its |
| /// instructions are allowed to produce different results on different |
| /// engines. A module containing one only runs on an engine implementing the |
| /// proposal, so using them is opt-in per module. |
| final bool allowRelaxedSimd; |
| |
| /// Create a new, initially empty, module. |
| /// |
| /// The [watchPoints] is a list of byte offsets within the final module of |
| /// bytes to watch. When the module is serialized, the stack traces leading |
| /// to the production of all watched bytes are printed. This can be used to |
| /// debug runtime errors happening at specific offsets within the module. |
| ModuleBuilder( |
| this.moduleName, |
| this.sourceMapUrl, { |
| ModuleBuilder? parent, |
| this.watchPoints = const [], |
| this.allowRelaxedSimd = false, |
| }) : debugInfoTables = sourceMapUrl == null ? null : DebugInfoTables() { |
| types = TypesBuilder(parent: parent?.types); |
| } |
| |
| void addCustomSection(String name, Uint8List data) { |
| _extraCustomSections.add(ExtraCustomSection(name, data)); |
| } |
| |
| /// Whether loading the module would have no effect. |
| /// |
| /// This means no code is executable and no external data would be modified. |
| bool get hasNoEffect { |
| // Exports can be used from the outside. |
| if (exports.hasExports) return false; |
| |
| // Start function is implicitly used from the outside - it's run at module |
| // instantiation time. |
| if (_startFunction != null) return false; |
| |
| // Active element segments are implicitly used from the outside - they are |
| // run at module instantiation time and may patch imported tables. |
| if (elements.hasActiveElementSegments) return false; |
| |
| return true; |
| } |
| |
| FunctionBuilder? get startFunctionIfCreated => _startFunction; |
| |
| FunctionBuilder get startFunction => _startFunction ??= functions.define( |
| types.defineFunction(const [], const []), |
| "#init", |
| ); |
| |
| @override |
| ir.Module forceBuild() { |
| if (_startFunction case final start?) { |
| start.body.end(); |
| start.build(); |
| } |
| final finalFunctions = functions.build(); |
| final finalTables = tables.build(); |
| final finalElements = elements.build(); |
| final finalMemories = memories.build(); |
| final finalGlobals = globals.build(); |
| final finalTags = tags.build(); |
| final finalExports = exports.build(); |
| final finalTypes = types.build( |
| finalFunctions, |
| finalTables, |
| finalGlobals, |
| finalTags, |
| ); |
| final finalDataSegments = dataSegments.build(); |
| final imports = ir.Imports( |
| finalFunctions.imported, |
| finalTags.imported, |
| finalGlobals.imported, |
| finalTables.imported, |
| finalMemories.imported, |
| ); |
| return module..initialize( |
| moduleName, |
| finalFunctions, |
| _startFunction?.function, |
| finalTables, |
| finalElements, |
| finalTags, |
| finalMemories, |
| finalExports, |
| finalGlobals, |
| finalTypes, |
| finalDataSegments, |
| imports, |
| watchPoints, |
| sourceMapUrl, |
| _extraCustomSections, |
| debugInfoTables, |
| ); |
| } |
| } |