HomeCurriculumTypeScriptCompiler Architecture, AST & Custom Transformers
Compiler Architecture, AST & Custom Transformers

Compiler Architecture, AST & Custom Transformers

tsconfig.json strict compiler flags, Abstract Syntax Trees (AST), compiler API, and custom transformers.

DT
Authored by DevThesis Systems Editorial Team
Reviewed for Technical Accuracy & Code Determinism

The TypeScript compiler (tsc) is one of the most sophisticated, high-performance static analysis tools ever constructed in software engineering. Comprising over 100,000 lines of highly optimized TypeScript code, tsc processes millions of lines of source code across modern enterprise monorepos in seconds, synthesizing Abstract Syntax Trees (ASTs), building unified symbol tables across disparate compilation units, performing multi-stage type inference, and emitting optimized ECMAScript and .d.ts declaration files.

For senior systems engineers, framework authors, and tooling architects, treating the TypeScript compiler as a black box limits potential. Unlocking custom metaprogramming, compile-time code generation, zero-cost dependency injection, and automated schema derivation requires an intimate, internal understanding of the Five Compiler Phases, the AST Hierarchy, the Symbol and Type Lattices, and the Compiler Transformation API.

This guide delivers an authoritative, university-grade architectural treatise on the internal lifecycle of the TypeScript compiler, AST manipulation, custom transformation pipelines, and incremental project graph compilation.


1. The Five Compiler Phases: From Text to JavaScript#

The TypeScript compilation pipeline is structured as a sequential five-stage pipeline:

text
+---------------------------------------------------------------------------------------+
|                               The TypeScript Compiler Pipeline                        |
|                                                                                       |
|   1. SOURCE CODE (e.g., app.ts)                                                       |
|         |                                                                             |
|         v                                                                             |
|   2. SCANNER (scanner.ts)                                                             |
|         | Converts character stream into a flat sequence of lexical tokens.           |
|         | Tokens: [Keyword_const, Identifier_x, Colon, Keyword_number, Equals, ...]    |
|         v                                                                             |
|   3. PARSER (parser.ts)                                                               |
|         | Transforms token sequence into an Abstract Syntax Tree (SourceFile AST).    |
|         | Nodes: VariableStatement -> VariableDeclaration -> BinaryExpression         |
|         v                                                                             |
|   4. BINDER (binder.ts)                                                               |
|         | Scans AST to generate Symbols, Symbol Tables, and Lexical Scopes.           |
|         | Binds declarations to identifiers across modules and namespaces.            |
|         v                                                                             |
|   5. TYPE CHECKER (checker.ts)                                                        |
|         | The heart of tsc (~45,000 lines). Performs structural type checking,       |
|         | subtyping resolution, flow-sensitive CFA, and diagnostic error reporting.   |
|         v                                                                             |
|   6. EMITTER (emitter.ts) & TRANSFORMERS                                              |
|         | Runs custom AST transformers, strips type annotations, downlevels syntax,   |
|         | and emits .js, .js.map, and .d.ts files.                                    |
+---------------------------------------------------------------------------------------+

Phase 1: The Scanner (Lexical Analyzer)#

  • Source file: src/compiler/scanner.ts
  • Operates on raw UTF-16 character streams.
  • Recognizes language tokens identified by the SyntaxKind enum (e.g., SyntaxKind.ConstKeyword, SyntaxKind.OpenParenToken, SyntaxKind.Identifier).
  • Implemented as a high-performance state machine avoiding dynamic string allocation where possible.

Phase 2: The Parser (Syntactic Analyzer)#

  • Source file: src/compiler/parser.ts
  • Employs a Recursive Descent Parser with Lookahead (LL(k)).
  • Reads tokens from the Scanner and constructs a concrete Abstract Syntax Tree (AST) rooted in a ts.SourceFile node.
  • Constructs parent-child node relationships and records exact source text offsets (pos and end) for source mapping and error squiggly generation.
  • Handles grammatical ambiguity between TypeScript syntax and JavaScript expressions (e.g., distinguishing between a JSX tag <Component /> and a generic call func<T>()).

Phase 3: The Binder (Semantic Identifier Binder)#

  • Source file: src/compiler/binder.ts
  • Operates in a single fast traversal over the AST.
  • Does not perform type checking. Instead, it creates Symbols:

- A ts.Symbol links an identifier declaration to its runtime meaning.

- A symbol can represent a Value, a Type, a Namespace, or combinations thereof (e.g., a class declares both a Value constructor and a Type instance).

  • Allocates SymbolTable mappings across lexical scopes (Global scope, Module scope, Function scope, Block scope).
  • Computes the Control Flow Graph (CFG) basic blocks used later by Control Flow Analysis.

Phase 4: The Type Checker (Semantic Analyzer)#

  • Source file: src/compiler/checker.ts
  • The most complex component of the TypeScript compiler.
  • Lazy Evaluation: The checker only computes types for nodes that are actually referenced. If a large imported library is only partially utilized, unreferenced declarations are never type-checked.
  • Instantiates ts.Type objects representing primitive types, object shapes, unions, intersections, and generic applications.
  • Executes structural assignability checking (checkTypeRelatedTo), overload resolution, and flow-sensitive type narrowing.

Phase 5: The Emitter & Transformer Pipeline#

  • Source file: src/compiler/emitter.ts
  • Passes the AST through a series of AST Transformers:

- Standard transformers: Stripping type annotations, converting TypeScript enums to JavaScript objects, downleveling ES2022 features (e.g., class fields, async/await) to target ES versions (ES5/ES6).

- Custom user transformers: Injected via the Compiler API or tools like ts-patch / ttypescript.

  • Writes out output files: JavaScript bundles, source maps, and .d.ts declaration trees.

2. Anatomy of the TypeScript Abstract Syntax Tree (AST)#

Every element in a TypeScript program is represented as a node implementing the ts.Node interface:

typescript
interface Node {
  kind: SyntaxKind;
  pos: number;          // Starting character offset in source text
  end: number;          // Ending character offset
  flags: NodeFlags;     // Bitwise flags (e.g., Let, Const, Yield, Await)
  parent?: Node;        // Pointer to enclosing parent node
}
text
+---------------------------------------------------------------------------------------+
|                               AST Node Hierarchy Decomposition                        |
|                                                                                       |
|   Statement:    const message: string = "Hello World";                                |
|                                                                                       |
|   AST Hierarchy:                                                                      |
|   ts.VariableStatement (kind: 240)                                                    |
|     +-- ts.VariableDeclarationList (kind: 258, flags: NodeFlags.Const)                |
|           +-- ts.VariableDeclaration (kind: 257)                                      |
|                 +-- ts.Identifier (kind: 79, text: "message")                         |
|                 +-- ts.TypeReferenceNode (kind: 180)                                  |
|                 |     +-- ts.Identifier (kind: 79, text: "string")                    |
|                 +-- ts.StringLiteral (kind: 10, text: "Hello World")                  |
+---------------------------------------------------------------------------------------+

To inspect an AST programmatically, developers use visitor patterns:

typescript
import * as ts from "typescript";

function printNodeRecursive(node: ts.Node, depth: number = 0) {
  const indent = "  ".repeat(depth);
  const syntaxName = ts.SyntaxKind[node.kind];
  console.log(`{indent}{syntaxName} [{node.pos} - {node.end}]`);

  // Recursively visit all child nodes
  ts.forEachChild(node, (child) => printNodeRecursive(child, depth + 1));
}

3. The TypeScript Transformer Architecture#

Custom AST Transformers provide the ultimate metaprogramming capability in TypeScript: they allow developers to intercept the AST after the type checker has validated semantic correctness, but before the emitter writes JavaScript to disk.

text
+---------------------------------------------------------------------------------------+
|                             Custom AST Transformer Pipeline                           |
|                                                                                       |
|   [ Source AST ]                                                                      |
|         |                                                                             |
|         v                                                                             |
|   +-------------------------------------------------------------------------------+   |
|   | Custom Transformer Factory: (context) => (sourceFile) => transformedSource    |   |
|   |                                                                               |   |
|   |   Visitor Function: ts.visitEachChild(node, visitor, context)                 |   |
|   |   - Inspects node.kind (e.g., MethodDeclaration)                              |   |
|   |   - Modifies or wraps node using ts.factory API                               |   |
|   +-------------------------------------------------------------------------------+   |
|         |                                                                             |
|         v                                                                             |
|   [ Transformed AST ] ---> [ Emitter: Writes Instrumented JavaScript to Disk ]        |
+---------------------------------------------------------------------------------------+

The Structure of a Transformer Factory#

A custom transformer is a higher-order function adhering to the ts.TransformerFactory<ts.SourceFile> contract:

typescript
import * as ts from "typescript";

export function createCustomTransformer(
  context: ts.TransformationContext
): ts.Transformer<ts.SourceFile> {
  const factory = context.factory;

  return (sourceFile: ts.SourceFile): ts.SourceFile => {
    function visitor(node: ts.Node): ts.Node {
      // 1. Inspect Node Kind
      if (ts.isCallExpression(node)) {
        // Intercept calls to logger.debug() and strip them in production builds
        if (isLoggerDebugCall(node)) {
          return factory.createVoidZero(); // Replace with 'void 0;'
        }
      }

      // 2. Recursively visit child nodes
      return ts.visitEachChild(node, visitor, context);
    }

    return ts.visitNode(sourceFile, visitor) as ts.SourceFile;
  };
}

function isLoggerDebugCall(node: ts.CallExpression): boolean {
  if (ts.isPropertyAccessExpression(node.expression)) {
    return (
      node.expression.name.text === "debug" &&
      ts.isIdentifier(node.expression.expression) &&
      node.expression.expression.text === "logger"
    );
  }
  return false;
}

4. The `ts.factory` API: Synthesizing Nodes#

When transforming ASTs, developers must never mutate existing node properties directly. In the TypeScript compiler architecture, AST nodes are immutable value objects.

To modify the tree, developers instantiate new replacement nodes using the ts.factory builder:

typescript
// Synthesizing: console.log("Execution Time: ", elapsed);
const logStatement = context.factory.createExpressionStatement(
  context.factory.createCallExpression(
    context.factory.createPropertyAccessExpression(
      context.factory.createIdentifier("console"),
      context.factory.createIdentifier("log")
    ),
    /* typeArguments */ undefined,
    /* argumentsArray */ [
      context.factory.createStringLiteral("Execution Time: "),
      context.factory.createIdentifier("elapsed")
    ]
  )
);

5. Symbols, Types, and the Checker API#

Beyond basic syntactic AST manipulation, advanced tooling requires Semantic Type Awareness. For instance, a dependency-injection transformer needs to know: "Does this parameter implement the `DatabaseConnection` interface?"

The AST alone cannot answer this because the AST only stores raw identifier tokens. Answering semantic questions requires querying the Type Checker:

typescript
// 1. Create a TypeScript Program
const program = ts.createProgram(["src/index.ts"], { target: ts.ScriptTarget.ES2022 });
const checker = program.getTypeChecker();
const sourceFile = program.getSourceFile("src/index.ts")!;

// 2. Query Type of an AST Node
function inspectIdentifierType(node: ts.Identifier) {
  // Retrieve Symbol
  const symbol = checker.getSymbolAtLocation(node);
  if (!symbol) return;

  // Retrieve Type
  const type = checker.getTypeOfSymbolAtLocation(symbol, node);
  const typeString = checker.typeToString(type);

  console.log(`Identifier '{node.text}' has static type: {typeString}`);
}

6. Programmatic AST Benchmark Transformer in TypeScript#

Below is a complete, production-ready custom AST transformer that automatically injects execution timing instrumentation around methods decorated with @Benchmark:

typescript
// Production Benchmark Instrumentation Transformer
import * as ts from "typescript";

export function benchmarkTransformer(
  context: ts.TransformationContext
): ts.Transformer<ts.SourceFile> {
  const { factory } = context;

  return (sourceFile: ts.SourceFile): ts.SourceFile => {
    function visitor(node: ts.Node): ts.Node {
      // Intercept Method Declarations with bodies
      if (ts.isMethodDeclaration(node) && node.body) {
        const methodName = node.name.getText(sourceFile);

        // Check if method is decorated or annotated with benchmark
        const hasBenchmarkComment = sourceFile.text
          .slice(node.pos, node.body.pos)
          .includes("@benchmark");

        if (hasBenchmarkComment) {
          const timerLabel = factory.createStringLiteral(`Benchmark: {methodName}`);

          // Synthesize: console.time("Benchmark: methodName");
          const startTime = factory.createExpressionStatement(
            factory.createCallExpression(
              factory.createPropertyAccessExpression(
                factory.createIdentifier("console"),
                factory.createIdentifier("time")
              ),
              undefined,
              [timerLabel]
            )
          );

          // Synthesize: console.timeEnd("Benchmark: methodName");
          const endTime = factory.createExpressionStatement(
            factory.createCallExpression(
              factory.createPropertyAccessExpression(
                factory.createIdentifier("console"),
                factory.createIdentifier("timeEnd")
              ),
              undefined,
              [timerLabel]
            )
          );

          // Wrap existing method statements inside try ... finally
          const tryFinallyBlock = factory.createTryStatement(
            node.body,
            /* catchClause */ undefined,
            factory.createBlock([endTime], true)
          );

          // Replace method body
          const instrumentedBody = factory.createBlock(
            [startTime, tryFinallyBlock],
            true
          );

          return factory.updateMethodDeclaration(
            node,
            node.modifiers,
            node.asteriskToken,
            node.name,
            node.questionToken,
            node.typeParameters,
            node.parameters,
            node.type,
            instrumentedBody
          );
        }
      }

      return ts.visitEachChild(node, visitor, context);
    }

    return ts.visitNode(sourceFile, visitor) as ts.SourceFile;
  };
}

7. Checkpoint Practice Problems & Applied Walkthroughs#

Consolidate your mastery of compiler architecture and AST transformation through four comprehensive multi-step mathematical problems.

Problem 1: AST Node Structure & SyntaxKind Decomposition#

Consider the following single line of TypeScript source code:

typescript
const total: number = a + 10;
  • Tasks Required:

1. Deconstruct this statement into its complete, hierarchical Abstract Syntax Tree (AST).

2. For each node in the tree, specify its canonical ts.SyntaxKind name.

3. Identify which node represents the binary operator and specify its left and right operands.

4. Write the programmatic condition using TypeScript compiler type guards (ts.is*) to isolate the numeric literal 10.

Step-by-Step Solution:

Step 1 & 2: Complete AST Hierarchy & SyntaxKind Mapping:

text
ts.SourceFile
  |
  +-- ts.VariableStatement (SyntaxKind.VariableStatement)
        |
        +-- ts.VariableDeclarationList (SyntaxKind.VariableDeclarationList)
              |  [flags: NodeFlags.Const]
              |
              +-- ts.VariableDeclaration (SyntaxKind.VariableDeclaration)
                    |
                    +-- name: ts.Identifier (SyntaxKind.Identifier) [text: "total"]
                    |
                    +-- type: ts.TypeReferenceNode / Keyword (SyntaxKind.NumberKeyword)
                    |
                    +-- initializer: ts.BinaryExpression (SyntaxKind.BinaryExpression)
                          |
                          +-- left: ts.Identifier (SyntaxKind.Identifier) [text: "a"]
                          |
                          +-- operatorToken: ts.Token (SyntaxKind.PlusToken)
                          |
                          +-- right: ts.NumericLiteral (SyntaxKind.NumericLiteral) [text: "10"]

Step 3: Identification of Binary Operator Node:

  • The binary operator node is `ts.BinaryExpression`.
  • left: ts.Identifier with text "a".
  • operatorToken: SyntaxKind.PlusToken (+).
  • right: ts.NumericLiteral with text "10".

Step 4: Programmatic Isolation of the Numeric Literal:

typescript
function findNumericLiteral(node: ts.Node): ts.NumericLiteral | null {
  if (ts.isNumericLiteral(node) && node.text === "10") {
    return node;
  }
  return ts.forEachChild(node, findNumericLiteral) ?? null;
}

Problem 2: Project References & Incremental Build Graph Scheduling#

An enterprise monorepo consists of 6 TypeScript packages organized with Project References (composite: true in tsconfig.json):

  • core: Base domain models (Depends on: none)
  • utils: Utility functions (Depends on: none)
  • db: Database client (Depends on: core, utils)
  • auth: Authentication microservice (Depends on: core, db)
  • api: REST API gateway (Depends on: core, utils, db, auth)
  • frontend: Web client (Depends on: core, utils)

Each package takes compilation time t_i:

  • t_{core} = 4.0 sec
  • t_{utils} = 2.0 sec
  • t_{db} = 6.0 sec
  • t_{auth} = 5.0 sec
  • t_{api} = 8.0 sec
  • t_{frontend} = 7.0 sec
  • Tasks Required:

1. Draw the directed acyclic dependency graph (DAG) representing the monorepo build topology.

2. Compute the Critical Path Duration (minimum possible compilation time assuming infinite parallel CPU cores).

3. If a developer alters a type definition inside core, determine the exact set of packages that must be recompiled by tsc --build.

4. If a developer alters auth, calculate the time saved by incremental compilation versus a full clean build of all packages.

Step-by-Step Solution:

Step 1: Directed Acyclic Graph (DAG) Structure:

Dependencies (Arrow X → Y indicates Y depends on X, so X must build before Y):

  • core → db, auth, api, frontend
  • utils → db, api, frontend
  • db → auth, api
  • auth → api

Step 2: Critical Path Calculation:

Let us compute the longest path from source nodes (core, utils) to the final sink node (api):

  • Path 1: core (4s) → frontend (7s) = 11.0s.
  • Path 2: utils (2s) → frontend (7s) = 9.0s.
  • Path 3: core (4s) → db (6s) → api (8s) = 18.0s.
  • Path 4: core (4s) → db (6s) → auth (5s) → api (8s):

Duration = 4.0 + 6.0 + 5.0 + 8.0 = 23.0 seconds.

Critical Path: core → db → auth → api.

Minimum Critical Path Time = 23.0 seconds.

Step 3: Invalidation Set on `core` Modification:

Because core is at the absolute foundation:

  • db depends on core -> Must rebuild.
  • auth depends on core and db -> Must rebuild.
  • api depends on core, db, auth -> Must rebuild.
  • frontend depends on core -> Must rebuild.
  • utils does not depend on core -> Skipped! (Cached via .tsbuildinfo).

Rebuild Set: `core`, `db`, `auth`, `api`, `frontend` (5 of 6 packages).

Step 4: Incremental Compilation Savings on `auth` Modification:

Total Clean Build Time (Sequential sum of all 6 packages):

T_{clean} = 4.0 + 2.0 + 6.0 + 5.0 + 8.0 + 7.0 = 32.0 seconds.

If only auth is modified:

  • core, utils, db, and frontend have valid .tsbuildinfo hashes -> SKIPPED (0s).
  • Packages needing rebuild:

1. auth (modified): 5.0s.

2. api (depends on auth): 8.0s.

  • Incremental Rebuild Time:

T_{incremental} = 5.0 + 8.0 = 13.0 seconds.

Time Saved:

Δ T = 32.0 - 13.0 = 19.0 seconds (59.38% reduction in CI build latency).


Problem 3: Type Checker Symbol Space Collision & Declaration Merging#

In TypeScript, declarations contribute to three distinct semantic spaces:

  1. Value Space: Accessible at runtime (variables, functions, classes).
  2. Type Space: Accessible at compile time (interfaces, type aliases).
  3. Namespace Space: Accessible for nested member resolution.

Consider the following valid TypeScript code:

typescript
interface UserConfig {
  apiKey: string;
}

function UserConfig(key: string): UserConfig {
  return { apiKey: key };
}

namespace UserConfig {
  export const DEFAULT_TIMEOUT_MS = 5000;
}
  • Tasks Required:

1. Analyze which semantic spaces are populated by each of the three UserConfig declarations.

2. Explain why the TypeScript compiler allows an interface, a function, and a namespace to share the exact identical name within the same lexical scope without raising a duplicate identifier error.

3. If a developer attempts to add:

type UserConfig = { apiKey: string };

state which semantic space experiences a collision and write the exact compiler error.

Step-by-Step Solution:

Step 1: Semantic Space Attribution:

  • interface UserConfig: Populates Type Space exclusively. (Has zero runtime representation).
  • function UserConfig(...): Populates Value Space exclusively. (Callable function at runtime).
  • namespace UserConfig: Populates Namespace Space AND Value Space (attaches DEFAULT_TIMEOUT_MS as a property of the UserConfig runtime function object: UserConfig.DEFAULT_TIMEOUT_MS = 5000).

Step 2: Why Declaration Merging Succeeds:

Under the TypeScript language specification (Section 11.2):

  • A symbol can inhabit distinct semantic spaces simultaneously.
  • When an identifier is referenced:

- In a type position (const x: UserConfig = ...), the compiler queries the Type Space, resolving to the interface.

- In a value position (const x = UserConfig("abc")), the compiler queries the Value Space, resolving to the function.

- In a property lookup (UserConfig.DEFAULT_TIMEOUT_MS), the compiler queries the Namespace / Value Space, resolving to the exported constant.

Because interface occupies Type Space while function and namespace merge cleanly in Value Space without property collisions, the declaration merge is 100% legal.

Step 3: Impact of Adding `type UserConfig = ...`:

  • A type alias populates Type Space.
  • However, interface UserConfig already occupies Type Space with the name UserConfig.
  • While multiple interfaces merge automatically, a type alias is closed and non-merging.
  • Collision Space: Type Space.
  • Compiler Error:

```

Duplicate identifier 'UserConfig'.

```


Problem 4: Synthesizing Type-Safe GraphQL Resolvers via AST Transformation#

An enterprise backend compiles TypeScript resolver classes into GraphQL schemas. A developer writes an AST transformer that automatically analyzes method return types and injects runtime metadata:

typescript
class ProductResolver {
  // Target method to inspect
  public async getProductDetails(id: string): Promise<ProductRecord> {
    return fetchProduct(id);
  }
}
  • Tasks Required:

1. Write the AST traversal condition to identify all methods whose return type is wrapped inside a Promise<T>.

2. Using the ts.TypeChecker, demonstrate how to extract the underlying unwrapped entity type (ProductRecord) from the Promise.

3. Show the ts.factory call to append a static property ProductResolver.__graphQLMetadata containing the extracted type name string "ProductRecord".

Step-by-Step Solution:

Step 1: Traverse and Identify Promise Methods:

typescript
function isPromiseReturningMethod(node: ts.MethodDeclaration, checker: ts.TypeChecker): boolean {
  if (!node.type) return false;

  // Query Type from TypeChecker
  const returnType = checker.getTypeFromTypeNode(node.type);
  const symbol = returnType.getSymbol();

  return symbol?.name === "Promise";
}

Step 2: Unpack Generic Type Argument:

typescript
function extractUnwrappedTypeName(node: ts.MethodDeclaration, checker: ts.TypeChecker): string | null {
  if (!ts.isTypeReferenceNode(node.type!)) return null;

  // Read first type argument: Promise<ProductRecord> -> ProductRecord
  const typeArgs = node.type.typeArguments;
  if (!typeArgs || typeArgs.length === 0) return null;

  const innerTypeNode = typeArgs[0];
  const innerType = checker.getTypeFromTypeNode(innerTypeNode);

  return checker.typeToString(innerType);
}

Step 3: Synthesize Static Metadata Property via `ts.factory`:

To append:

ProductResolver.__graphQLMetadata = { returnTypeName: "ProductRecord" };

typescript
function createMetadataAssignment(
  factory: ts.NodeFactory,
  className: string,
  returnTypeName: string
): ts.ExpressionStatement {
  return factory.createExpressionStatement(
    factory.createBinaryExpression(
      factory.createPropertyAccessExpression(
        factory.createIdentifier(className),
        factory.createIdentifier("__graphQLMetadata")
      ),
      ts.SyntaxKind.EqualsToken,
      factory.createObjectLiteralExpression([
        factory.createPropertyAssignment(
          factory.createIdentifier("returnTypeName"),
          factory.createStringLiteral(returnTypeName)
        )
      ], true)
    )
  );
}

Engineering Takeaway: Custom AST transformers combined with the semantic TypeChecker enable production-grade, zero-boilerplate metaprogramming, automatically maintaining synchronized runtime metadata directly from compile-time TypeScript type definitions.

Key Architectural Takeaways

  • Mastering Compiler Architecture, AST & Custom Transformers is critical for TypeScript production systems.
  • Strict invariant verification guarantees runtime stability and predictability.
  • Always follow industry-standard telemetry, logging, and security practices.

Hands-on Code Playground Available

Execute, edit, and experiment with code for this topic in the live sandbox.

Practice Check • Quick Quiz

Test Your Understanding

In TypeScript, what is the main purpose of Compiler Architecture, AST & Custom Transformers?

A
To guarantee deterministic performance and correctness for compiler architecture, ast & custom transformers.
B
To bypass all system security checks during execution.
C
To increase memory allocation overhead unnecessarily.
D
To force non-blocking single-threaded execution globally.