Building SQLite for WASM Fails with “must use ‘struct’ tag to refer to type ‘Wal'” in Non-Amalgamation Configurations
Compilation Error Due to Missing Type Declaration in WAL Module
Issue Overview
A compilation error occurs when building SQLite for WebAssembly (WASM) with the --enable-amalgamation=no flag, specifically in the Write-Ahead Logging (WAL) module. The error manifests as:
/projects/sqlite/repo/bld/../src/wal.c:747:3: error: must use 'struct' tag to refer to type 'Wal'
747 | Wal *pWal, /* The WAL context */
This error arises because the compiler encounters an incomplete or unrecognized declaration of the Wal struct type. The root cause involves mismatched handling of the SQLITE_OMIT_WAL preprocessor directive across header and source files, particularly in non-amalgamation builds.
In standard amalgamation builds (--enable-amalgamation=yes, the default), all SQLite code is combined into a single sqlite3.c file, ensuring headers are included in a specific order. Non-amalgamation builds compile individual source files separately, exposing dependencies on header inclusion order and preprocessor flag propagation. The WASM build environment typically requires SQLITE_OMIT_WAL to be defined due to platform constraints (e.g., lack of shared memory support), but this definition is not consistently applied early enough in wal.c when using split source files.
The error occurs at the point where the Wal struct is referenced before its full declaration is visible. In SQLite’s source organization, wal.h declares Wal conditionally based on SQLITE_OMIT_WAL, but wal.c does not include sqlite3.h (where SQLITE_OMIT_WAL is often defined) until later in the file. This creates a scenario where the compiler parses references to Wal before knowing whether the struct is declared or omitted.
Mismatched Preprocessor Directives and Header Inclusion Order
Inconsistent Definition of SQLITE_OMIT_WAL
The SQLITE_OMIT_WAL macro controls whether WAL-related code is included in the build. When building for WASM, this flag must be defined to disable unsupported WAL features. However, in non-amalgamation builds:
- Header Inclusion Order: The
wal.cfile includessqlite3.hafter local headers likewal.h, delaying visibility ofSQLITE_OMIT_WAL. Ifwal.hchecks forSQLITE_OMIT_WALbefore it is defined, theWalstruct declaration is omitted, causing later references toWalinwal.cto fail. - Build System Configuration: The WASM build environment may define
SQLITE_OMIT_WALin platform-specific configuration files (e.g.,sqlite3.hoverrides) that are not processed early enough in non-amalgamation builds.
Amalgamation vs. Non-Amalgamation Build Differences
In amalgamation builds:
- All headers are included in a controlled order within
sqlite3.c, ensuringSQLITE_OMIT_WALis defined beforewal.his processed. - The
Walstruct declaration is either included or omitted consistently.
In non-amalgamation builds:
- Each source file (e.g.,
wal.c) includes headers independently. - If
wal.cdoes not includesqlite3.hbefore referencingWal, the compiler encounters an undeclared type.
Platform-Specific Configuration Challenges
The SQLite build system does not inherently detect WASM/WASI (WebAssembly System Interface) environments. Developers must manually configure flags like SQLITE_OMIT_WAL, but these flags may not propagate correctly to all source files in split builds. For example:
- Autotools-based builds (e.g.,
configure --with-wasi-sdk=X) may not injectSQLITE_OMIT_WALinto compiler flags. - Platform-specific
sqlite3.hmodifications (e.g., unconditional#define SQLITE_OMIT_WALfor WASM) may be overridden by later includes or build steps.
Resolving Header Dependencies and Build Configuration
Step 1: Validate SQLITE_OMIT_WAL Propagation
Confirm that SQLITE_OMIT_WAL is defined throughout the build process:
- Inspect Compiler Command Lines:
Ensure-DSQLITE_OMIT_WALis present when compilingwal.c. For example:gcc -c -DSQLITE_OMIT_WAL -I/path/to/sqlite/includes wal.c -o wal.o - Check
sqlite3.hConfiguration:
Verify thatsqlite3.hunconditionally definesSQLITE_OMIT_WALfor WASM builds. For example:#if defined(__EMSCRIPTEN__) || defined(__wasi__) #define SQLITE_OMIT_WAL 1 #endif
Step 2: Adjust Header Inclusion Order in wal.c
Modify wal.c to include sqlite3.h before any local headers:
// Add at the very top of wal.c
#include "sqlite3.h"
// Existing includes
#include "wal.h"
This ensures SQLITE_OMIT_WAL is defined before wal.h is processed, allowing the latter to conditionally omit Wal declarations.
Step 3: Use Split Amalgamation for Large File Handling
If non-amalgamation builds are required to avoid "too many lines" errors in tooling:
-
Generate Split Amalgamation:
Use SQLite’ssplit-sqlite3c.tclscript (or equivalent) to dividesqlite3.cinto smaller files:tclsh split-sqlite3c.tclThis creates
sqlite3-1.c,sqlite3-2.c, etc., which can be compiled individually without losing amalgamation benefits. -
Update Build System:
Replace references to individual source files with the split amalgamation outputs.
Step 4: Patch Build Configuration for WASI
For configure-based builds targeting WASI:
- Modify
configure.ac:
Add logic to detect WASI and automatically defineSQLITE_OMIT_WAL:AC_TRY_COMPILE([], [ #if !defined(__wasi__) #error "Not WASI" #endif ], [AC_DEFINE([SQLITE_OMIT_WAL], [1], [Disable WAL for WASI])]) - Rebuild Autotools Configuration:
Runautoreconf -fito regenerateconfigurewith the new logic.
Step 5: Workaround via Explicit Compiler Flags
If modifying source files is undesirable, pass SQLITE_OMIT_WAL explicitly:
./configure --enable-amalgamation=no CFLAGS="-DSQLITE_OMIT_WAL"
This ensures the flag is defined globally, regardless of header inclusion order.
Step 6: Revert to Amalgamation Builds
If non-amalgamation builds are not strictly necessary:
./configure --enable-amalgamation=yes
Amalgamation builds avoid header order issues by design.
Step 7: Monitor Upstream SQLite Changes
The SQLite team has experimented with removing hard-coded SQLITE_OMIT_WAL definitions for WASM. Track these changes:
- Update to Latest Trunk:
fossil update trunk - Test for Regressions:
Ensure WASM builds still function correctly after updates. IfSQLITE_OMIT_WALis no longer defined, re-add it via compiler flags.
Step 8: Custom Source Patching
For environments requiring long-term stability:
- Apply a Local Patch to
wal.c:--- a/src/wal.c +++ b/src/wal.c @@ -16,6 +16,7 @@ ** ** This file contains the implementation of a write-ahead log (WAL) used in ** "journal_mode=WAL" mode. */ +#include "sqlite3.h" #include "sqliteInt.h" #include "wal.h" - Maintain the Patch:
Use version control or a patch management system to reapply this change during updates.
Final Recommendations
- Prefer Amalgamation Builds: They minimize header-related issues and are SQLite’s primary supported configuration.
- Explicitly Define
SQLITE_OMIT_WAL: Use compiler flags to ensure consistent behavior across build types. - Contribute Upstream: If split amalgamation or WASM configuration improvements are critical for your project, collaborate with the SQLite team to integrate robust solutions.
By addressing header inclusion order, build flag consistency, and platform detection, developers can resolve the "struct Wal" compilation error while maintaining compatibility with WASM/WASI constraints.