Add ProgramID feed it into all ast::Nodes

This will be used to detect accidental leaks of program objects between programs.

Bug: tint:709
Change-Id: I20f784a2c673d19a04a880b3ec91dfe2eb743bdb
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47622
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 2e27469..53f32c5 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -362,6 +362,8 @@
     "inspector/scalar.h",
     "intrinsic_table.cc",
     "intrinsic_table.h",
+    "program_id.cc",
+    "program_id.h",
     "program.cc",
     "program.h",
     "program_builder.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4852f94..a691c62 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -179,6 +179,8 @@
   inspector/scalar.h
   program_builder.cc
   program_builder.h
+  program_id.cc
+  program_id.h
   program.cc
   program.h
   reader/reader.cc
diff --git a/src/ast/access_decoration.cc b/src/ast/access_decoration.cc
index b01c44b..726a93e 100644
--- a/src/ast/access_decoration.cc
+++ b/src/ast/access_decoration.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-AccessDecoration::AccessDecoration(const Source& source, AccessControl val)
-    : Base(source), value_(val) {}
+AccessDecoration::AccessDecoration(ProgramID program_id,
+                                   const Source& source,
+                                   AccessControl val)
+    : Base(program_id, source), value_(val) {}
 
 AccessDecoration::~AccessDecoration() = default;
 
diff --git a/src/ast/access_decoration.h b/src/ast/access_decoration.h
index fd7697f..ce7183c 100644
--- a/src/ast/access_decoration.h
+++ b/src/ast/access_decoration.h
@@ -25,9 +25,12 @@
 class AccessDecoration : public Castable<AccessDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param value the access value
-  explicit AccessDecoration(const Source& source, AccessControl value);
+  AccessDecoration(ProgramID program_id,
+                   const Source& source,
+                   AccessControl value);
   ~AccessDecoration() override;
 
   /// @returns the access control value
diff --git a/src/ast/array_accessor_expression.cc b/src/ast/array_accessor_expression.cc
index 5424a98..212350b 100644
--- a/src/ast/array_accessor_expression.cc
+++ b/src/ast/array_accessor_expression.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-ArrayAccessorExpression::ArrayAccessorExpression(const Source& source,
+ArrayAccessorExpression::ArrayAccessorExpression(ProgramID program_id,
+                                                 const Source& source,
                                                  Expression* array,
                                                  Expression* idx_expr)
-    : Base(source), array_(array), idx_expr_(idx_expr) {
+    : Base(program_id, source), array_(array), idx_expr_(idx_expr) {
   TINT_ASSERT(array_);
   TINT_ASSERT(idx_expr_);
 }
diff --git a/src/ast/array_accessor_expression.h b/src/ast/array_accessor_expression.h
index 24a13ec..5e83b76 100644
--- a/src/ast/array_accessor_expression.h
+++ b/src/ast/array_accessor_expression.h
@@ -25,10 +25,12 @@
     : public Castable<ArrayAccessorExpression, Expression> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the array accessor source
   /// @param array the array
   /// @param idx_expr the index expression
-  ArrayAccessorExpression(const Source& source,
+  ArrayAccessorExpression(ProgramID program_id,
+                          const Source& source,
                           Expression* array,
                           Expression* idx_expr);
   /// Move constructor
diff --git a/src/ast/assignment_statement.cc b/src/ast/assignment_statement.cc
index 8e98c23..68c2cc9 100644
--- a/src/ast/assignment_statement.cc
+++ b/src/ast/assignment_statement.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-AssignmentStatement::AssignmentStatement(const Source& source,
+AssignmentStatement::AssignmentStatement(ProgramID program_id,
+                                         const Source& source,
                                          Expression* lhs,
                                          Expression* rhs)
-    : Base(source), lhs_(lhs), rhs_(rhs) {
+    : Base(program_id, source), lhs_(lhs), rhs_(rhs) {
   TINT_ASSERT(lhs_);
   TINT_ASSERT(rhs_);
 }
diff --git a/src/ast/assignment_statement.h b/src/ast/assignment_statement.h
index a7a0365..dc2ab47 100644
--- a/src/ast/assignment_statement.h
+++ b/src/ast/assignment_statement.h
@@ -25,10 +25,14 @@
 class AssignmentStatement : public Castable<AssignmentStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the assignment statement source
   /// @param lhs the left side of the expression
   /// @param rhs the right side of the expression
-  AssignmentStatement(const Source& source, Expression* lhs, Expression* rhs);
+  AssignmentStatement(ProgramID program_id,
+                      const Source& source,
+                      Expression* lhs,
+                      Expression* rhs);
   /// Move constructor
   AssignmentStatement(AssignmentStatement&&);
   ~AssignmentStatement() override;
diff --git a/src/ast/binary_expression.cc b/src/ast/binary_expression.cc
index 2426eb9..63a3320 100644
--- a/src/ast/binary_expression.cc
+++ b/src/ast/binary_expression.cc
@@ -21,11 +21,12 @@
 namespace tint {
 namespace ast {
 
-BinaryExpression::BinaryExpression(const Source& source,
+BinaryExpression::BinaryExpression(ProgramID program_id,
+                                   const Source& source,
                                    BinaryOp op,
                                    Expression* lhs,
                                    Expression* rhs)
-    : Base(source), op_(op), lhs_(lhs), rhs_(rhs) {
+    : Base(program_id, source), op_(op), lhs_(lhs), rhs_(rhs) {
   TINT_ASSERT(lhs_);
   TINT_ASSERT(rhs_);
   TINT_ASSERT(op_ != BinaryOp::kNone);
diff --git a/src/ast/binary_expression.h b/src/ast/binary_expression.h
index 6eec7ae..f7a2ddf 100644
--- a/src/ast/binary_expression.h
+++ b/src/ast/binary_expression.h
@@ -47,11 +47,13 @@
 class BinaryExpression : public Castable<BinaryExpression, Expression> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the binary expression source
   /// @param op the operation type
   /// @param lhs the left side of the expression
   /// @param rhs the right side of the expression
-  BinaryExpression(const Source& source,
+  BinaryExpression(ProgramID program_id,
+                   const Source& source,
                    BinaryOp op,
                    Expression* lhs,
                    Expression* rhs);
diff --git a/src/ast/binding_decoration.cc b/src/ast/binding_decoration.cc
index 3a385f6..4003189 100644
--- a/src/ast/binding_decoration.cc
+++ b/src/ast/binding_decoration.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-BindingDecoration::BindingDecoration(const Source& source, uint32_t val)
-    : Base(source), value_(val) {}
+BindingDecoration::BindingDecoration(ProgramID program_id,
+                                     const Source& source,
+                                     uint32_t val)
+    : Base(program_id, source), value_(val) {}
 
 BindingDecoration::~BindingDecoration() = default;
 
diff --git a/src/ast/binding_decoration.h b/src/ast/binding_decoration.h
index f552b88..197cc2a 100644
--- a/src/ast/binding_decoration.h
+++ b/src/ast/binding_decoration.h
@@ -24,9 +24,10 @@
 class BindingDecoration : public Castable<BindingDecoration, Decoration> {
  public:
   /// constructor
-  /// @param value the binding value
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
-  BindingDecoration(const Source& source, uint32_t value);
+  /// @param value the binding value
+  BindingDecoration(ProgramID program_id, const Source& source, uint32_t value);
   ~BindingDecoration() override;
 
   /// @returns the binding value
diff --git a/src/ast/bitcast_expression.cc b/src/ast/bitcast_expression.cc
index 9d73fe4..c117c3c 100644
--- a/src/ast/bitcast_expression.cc
+++ b/src/ast/bitcast_expression.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-BitcastExpression::BitcastExpression(const Source& source,
+BitcastExpression::BitcastExpression(ProgramID program_id,
+                                     const Source& source,
                                      type::Type* type,
                                      Expression* expr)
-    : Base(source), type_(type), expr_(expr) {
+    : Base(program_id, source), type_(type), expr_(expr) {
   TINT_ASSERT(type_);
   TINT_ASSERT(expr_);
 }
diff --git a/src/ast/bitcast_expression.h b/src/ast/bitcast_expression.h
index 0e83b91..87c1018 100644
--- a/src/ast/bitcast_expression.h
+++ b/src/ast/bitcast_expression.h
@@ -24,10 +24,14 @@
 class BitcastExpression : public Castable<BitcastExpression, Expression> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the bitcast expression source
   /// @param type the type
   /// @param expr the expr
-  BitcastExpression(const Source& source, type::Type* type, Expression* expr);
+  BitcastExpression(ProgramID program_id,
+                    const Source& source,
+                    type::Type* type,
+                    Expression* expr);
   /// Move constructor
   BitcastExpression(BitcastExpression&&);
   ~BitcastExpression() override;
diff --git a/src/ast/block_statement.cc b/src/ast/block_statement.cc
index d2cdd93..38fdbbf 100644
--- a/src/ast/block_statement.cc
+++ b/src/ast/block_statement.cc
@@ -21,9 +21,10 @@
 namespace tint {
 namespace ast {
 
-BlockStatement::BlockStatement(const Source& source,
+BlockStatement::BlockStatement(ProgramID program_id,
+                               const Source& source,
                                const StatementList& statements)
-    : Base(source), statements_(std::move(statements)) {
+    : Base(program_id, source), statements_(std::move(statements)) {
   for (auto* stmt : *this) {
     TINT_ASSERT(stmt);
   }
diff --git a/src/ast/block_statement.h b/src/ast/block_statement.h
index f50e175..7bf35af 100644
--- a/src/ast/block_statement.h
+++ b/src/ast/block_statement.h
@@ -26,9 +26,12 @@
 class BlockStatement : public Castable<BlockStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the block statement source
   /// @param statements the statements
-  BlockStatement(const Source& source, const StatementList& statements);
+  BlockStatement(ProgramID program_id,
+                 const Source& source,
+                 const StatementList& statements);
   /// Move constructor
   BlockStatement(BlockStatement&&);
   ~BlockStatement() override;
diff --git a/src/ast/bool_literal.cc b/src/ast/bool_literal.cc
index e6e2fb6..4944bec 100644
--- a/src/ast/bool_literal.cc
+++ b/src/ast/bool_literal.cc
@@ -21,8 +21,11 @@
 namespace tint {
 namespace ast {
 
-BoolLiteral::BoolLiteral(const Source& source, type::Type* type, bool value)
-    : Base(source, type), value_(value) {}
+BoolLiteral::BoolLiteral(ProgramID program_id,
+                         const Source& source,
+                         type::Type* type,
+                         bool value)
+    : Base(program_id, source, type), value_(value) {}
 
 BoolLiteral::~BoolLiteral() = default;
 
diff --git a/src/ast/bool_literal.h b/src/ast/bool_literal.h
index f07bbf7..37d88ce 100644
--- a/src/ast/bool_literal.h
+++ b/src/ast/bool_literal.h
@@ -26,10 +26,14 @@
 class BoolLiteral : public Castable<BoolLiteral, Literal> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the input source
   /// @param type the type of the literal
   /// @param value the bool literals value
-  BoolLiteral(const Source& source, type::Type* type, bool value);
+  BoolLiteral(ProgramID program_id,
+              const Source& source,
+              type::Type* type,
+              bool value);
   ~BoolLiteral() override;
 
   /// @returns true if the bool literal is true
diff --git a/src/ast/break_statement.cc b/src/ast/break_statement.cc
index 0824c66..08f96a1 100644
--- a/src/ast/break_statement.cc
+++ b/src/ast/break_statement.cc
@@ -21,7 +21,8 @@
 namespace tint {
 namespace ast {
 
-BreakStatement::BreakStatement(const Source& source) : Base(source) {}
+BreakStatement::BreakStatement(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
 
 BreakStatement::BreakStatement(BreakStatement&&) = default;
 
diff --git a/src/ast/break_statement.h b/src/ast/break_statement.h
index 1bc6148..ce61b52 100644
--- a/src/ast/break_statement.h
+++ b/src/ast/break_statement.h
@@ -24,8 +24,9 @@
 class BreakStatement : public Castable<BreakStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the break statement source
-  explicit BreakStatement(const Source& source);
+  BreakStatement(ProgramID program_id, const Source& source);
   /// Move constructor
   BreakStatement(BreakStatement&&);
   ~BreakStatement() override;
diff --git a/src/ast/builtin_decoration.cc b/src/ast/builtin_decoration.cc
index 9ae92cc..92abc97 100644
--- a/src/ast/builtin_decoration.cc
+++ b/src/ast/builtin_decoration.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-BuiltinDecoration::BuiltinDecoration(const Source& source, Builtin builtin)
-    : Base(source), builtin_(builtin) {}
+BuiltinDecoration::BuiltinDecoration(ProgramID program_id,
+                                     const Source& source,
+                                     Builtin builtin)
+    : Base(program_id, source), builtin_(builtin) {}
 
 BuiltinDecoration::~BuiltinDecoration() = default;
 
diff --git a/src/ast/builtin_decoration.h b/src/ast/builtin_decoration.h
index cbc9d06..60f1d09 100644
--- a/src/ast/builtin_decoration.h
+++ b/src/ast/builtin_decoration.h
@@ -25,9 +25,12 @@
 class BuiltinDecoration : public Castable<BuiltinDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param builtin the builtin value
-  BuiltinDecoration(const Source& source, Builtin builtin);
+  BuiltinDecoration(ProgramID program_id,
+                    const Source& source,
+                    Builtin builtin);
   ~BuiltinDecoration() override;
 
   /// @returns the builtin value
diff --git a/src/ast/call_expression.cc b/src/ast/call_expression.cc
index e58396b..26908eb 100644
--- a/src/ast/call_expression.cc
+++ b/src/ast/call_expression.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-CallExpression::CallExpression(const Source& source,
+CallExpression::CallExpression(ProgramID program_id,
+                               const Source& source,
                                Expression* func,
                                ExpressionList params)
-    : Base(source), func_(func), params_(params) {
+    : Base(program_id, source), func_(func), params_(params) {
   TINT_ASSERT(func_);
   for (auto* param : params_) {
     TINT_ASSERT(param);
diff --git a/src/ast/call_expression.h b/src/ast/call_expression.h
index f317a60..3097074 100644
--- a/src/ast/call_expression.h
+++ b/src/ast/call_expression.h
@@ -24,10 +24,14 @@
 class CallExpression : public Castable<CallExpression, Expression> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the call expression source
   /// @param func the function
   /// @param params the parameters
-  CallExpression(const Source& source, Expression* func, ExpressionList params);
+  CallExpression(ProgramID program_id,
+                 const Source& source,
+                 Expression* func,
+                 ExpressionList params);
   /// Move constructor
   CallExpression(CallExpression&&);
   ~CallExpression() override;
diff --git a/src/ast/call_statement.cc b/src/ast/call_statement.cc
index 5b52a44..4432e1b 100644
--- a/src/ast/call_statement.cc
+++ b/src/ast/call_statement.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-CallStatement::CallStatement(const Source& source, CallExpression* call)
-    : Base(source), call_(call) {
+CallStatement::CallStatement(ProgramID program_id,
+                             const Source& source,
+                             CallExpression* call)
+    : Base(program_id, source), call_(call) {
   TINT_ASSERT(call_);
 }
 
diff --git a/src/ast/call_statement.h b/src/ast/call_statement.h
index 5c948a8..64a248f 100644
--- a/src/ast/call_statement.h
+++ b/src/ast/call_statement.h
@@ -25,9 +25,12 @@
 class CallStatement : public Castable<CallStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the input source for the statement
   /// @param call the function
-  CallStatement(const Source& source, CallExpression* call);
+  CallStatement(ProgramID program_id,
+                const Source& source,
+                CallExpression* call);
   /// Move constructor
   CallStatement(CallStatement&&);
   ~CallStatement() override;
diff --git a/src/ast/case_statement.cc b/src/ast/case_statement.cc
index e95383c..eeba6c9 100644
--- a/src/ast/case_statement.cc
+++ b/src/ast/case_statement.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-CaseStatement::CaseStatement(const Source& source,
+CaseStatement::CaseStatement(ProgramID program_id,
+                             const Source& source,
                              CaseSelectorList selectors,
                              BlockStatement* body)
-    : Base(source), selectors_(selectors), body_(body) {
+    : Base(program_id, source), selectors_(selectors), body_(body) {
   TINT_ASSERT(body_);
 }
 
diff --git a/src/ast/case_statement.h b/src/ast/case_statement.h
index 6a61837..de589ad 100644
--- a/src/ast/case_statement.h
+++ b/src/ast/case_statement.h
@@ -30,10 +30,12 @@
 class CaseStatement : public Castable<CaseStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source information
   /// @param selectors the case selectors
   /// @param body the case body
-  CaseStatement(const Source& source,
+  CaseStatement(ProgramID program_id,
+                const Source& source,
                 CaseSelectorList selectors,
                 BlockStatement* body);
   /// Move constructor
diff --git a/src/ast/constant_id_decoration.cc b/src/ast/constant_id_decoration.cc
index d80a3aa..7754fef 100644
--- a/src/ast/constant_id_decoration.cc
+++ b/src/ast/constant_id_decoration.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-ConstantIdDecoration::ConstantIdDecoration(const Source& source, uint32_t val)
-    : Base(source), value_(val) {}
+ConstantIdDecoration::ConstantIdDecoration(ProgramID program_id,
+                                           const Source& source,
+                                           uint32_t val)
+    : Base(program_id, source), value_(val) {}
 
 ConstantIdDecoration::~ConstantIdDecoration() = default;
 
diff --git a/src/ast/constant_id_decoration.h b/src/ast/constant_id_decoration.h
index 90b165f..6529778 100644
--- a/src/ast/constant_id_decoration.h
+++ b/src/ast/constant_id_decoration.h
@@ -24,9 +24,12 @@
 class ConstantIdDecoration : public Castable<ConstantIdDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param val the constant_id value
-  ConstantIdDecoration(const Source& source, uint32_t val);
+  ConstantIdDecoration(ProgramID program_id,
+                       const Source& source,
+                       uint32_t val);
   ~ConstantIdDecoration() override;
 
   /// @returns the constant id value
diff --git a/src/ast/constructor_expression.cc b/src/ast/constructor_expression.cc
index 9df22c6..c8d4ea2 100644
--- a/src/ast/constructor_expression.cc
+++ b/src/ast/constructor_expression.cc
@@ -23,8 +23,9 @@
 
 ConstructorExpression::ConstructorExpression(ConstructorExpression&&) = default;
 
-ConstructorExpression::ConstructorExpression(const Source& source)
-    : Base(source) {}
+ConstructorExpression::ConstructorExpression(ProgramID program_id,
+                                             const Source& source)
+    : Base(program_id, source) {}
 
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/constructor_expression.h b/src/ast/constructor_expression.h
index 1b1b7bd..ce761ba 100644
--- a/src/ast/constructor_expression.h
+++ b/src/ast/constructor_expression.h
@@ -28,8 +28,9 @@
 
  protected:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the constructor source
-  explicit ConstructorExpression(const Source& source);
+  ConstructorExpression(ProgramID program_id, const Source& source);
   /// Move constructor
   ConstructorExpression(ConstructorExpression&&);
 
diff --git a/src/ast/continue_statement.cc b/src/ast/continue_statement.cc
index 64f2267..5b97a3f 100644
--- a/src/ast/continue_statement.cc
+++ b/src/ast/continue_statement.cc
@@ -21,7 +21,8 @@
 namespace tint {
 namespace ast {
 
-ContinueStatement::ContinueStatement(const Source& source) : Base(source) {}
+ContinueStatement::ContinueStatement(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
 
 ContinueStatement::ContinueStatement(ContinueStatement&&) = default;
 
diff --git a/src/ast/continue_statement.h b/src/ast/continue_statement.h
index f2be33a..4c8d887 100644
--- a/src/ast/continue_statement.h
+++ b/src/ast/continue_statement.h
@@ -24,8 +24,9 @@
 class ContinueStatement : public Castable<ContinueStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the continue statement source
-  explicit ContinueStatement(const Source& source);
+  ContinueStatement(ProgramID program_id, const Source& source);
   /// Move constructor
   ContinueStatement(ContinueStatement&&);
   ~ContinueStatement() override;
diff --git a/src/ast/decoration.h b/src/ast/decoration.h
index 724a07c..8618904 100644
--- a/src/ast/decoration.h
+++ b/src/ast/decoration.h
@@ -29,8 +29,10 @@
 
  protected:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
-  explicit Decoration(const Source& source) : Base(source) {}
+  Decoration(ProgramID program_id, const Source& source)
+      : Base(program_id, source) {}
 };
 
 /// A list of decorations
diff --git a/src/ast/discard_statement.cc b/src/ast/discard_statement.cc
index c31ed8d..9a8b2fe 100644
--- a/src/ast/discard_statement.cc
+++ b/src/ast/discard_statement.cc
@@ -21,7 +21,8 @@
 namespace tint {
 namespace ast {
 
-DiscardStatement::DiscardStatement(const Source& source) : Base(source) {}
+DiscardStatement::DiscardStatement(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
 
 DiscardStatement::DiscardStatement(DiscardStatement&&) = default;
 
diff --git a/src/ast/discard_statement.h b/src/ast/discard_statement.h
index c2c6bb9..23e0ce9 100644
--- a/src/ast/discard_statement.h
+++ b/src/ast/discard_statement.h
@@ -24,8 +24,9 @@
 class DiscardStatement : public Castable<DiscardStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the discard statement source
-  explicit DiscardStatement(const Source& source);
+  DiscardStatement(ProgramID program_id, const Source& source);
   /// Move constructor
   DiscardStatement(DiscardStatement&&);
   ~DiscardStatement() override;
diff --git a/src/ast/else_statement.cc b/src/ast/else_statement.cc
index 6706247..38955c4 100644
--- a/src/ast/else_statement.cc
+++ b/src/ast/else_statement.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-ElseStatement::ElseStatement(const Source& source,
+ElseStatement::ElseStatement(ProgramID program_id,
+                             const Source& source,
                              Expression* condition,
                              BlockStatement* body)
-    : Base(source), condition_(condition), body_(body) {
+    : Base(program_id, source), condition_(condition), body_(body) {
   TINT_ASSERT(body_);
 }
 
diff --git a/src/ast/else_statement.h b/src/ast/else_statement.h
index a2c07c3..f49ba99 100644
--- a/src/ast/else_statement.h
+++ b/src/ast/else_statement.h
@@ -27,10 +27,12 @@
 class ElseStatement : public Castable<ElseStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source information
   /// @param condition the else condition
   /// @param body the else body
-  ElseStatement(const Source& source,
+  ElseStatement(ProgramID program_id,
+                const Source& source,
                 Expression* condition,
                 BlockStatement* body);
   /// Move constructor
diff --git a/src/ast/expression.cc b/src/ast/expression.cc
index 57d546e..875f9d7 100644
--- a/src/ast/expression.cc
+++ b/src/ast/expression.cc
@@ -22,7 +22,8 @@
 namespace tint {
 namespace ast {
 
-Expression::Expression(const Source& source) : Base(source) {}
+Expression::Expression(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
 
 Expression::Expression(Expression&&) = default;
 
diff --git a/src/ast/expression.h b/src/ast/expression.h
index fbf1fe5..214f194 100644
--- a/src/ast/expression.h
+++ b/src/ast/expression.h
@@ -31,8 +31,9 @@
 
  protected:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of the expression
-  explicit Expression(const Source& source);
+  Expression(ProgramID program_id, const Source& source);
   /// Move constructor
   Expression(Expression&&);
 
diff --git a/src/ast/fallthrough_statement.cc b/src/ast/fallthrough_statement.cc
index d432212..a3eef90 100644
--- a/src/ast/fallthrough_statement.cc
+++ b/src/ast/fallthrough_statement.cc
@@ -21,8 +21,9 @@
 namespace tint {
 namespace ast {
 
-FallthroughStatement::FallthroughStatement(const Source& source)
-    : Base(source) {}
+FallthroughStatement::FallthroughStatement(ProgramID program_id,
+                                           const Source& source)
+    : Base(program_id, source) {}
 
 FallthroughStatement::FallthroughStatement(FallthroughStatement&&) = default;
 
diff --git a/src/ast/fallthrough_statement.h b/src/ast/fallthrough_statement.h
index cb586a0..ee4df99 100644
--- a/src/ast/fallthrough_statement.h
+++ b/src/ast/fallthrough_statement.h
@@ -24,8 +24,9 @@
 class FallthroughStatement : public Castable<FallthroughStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source information
-  explicit FallthroughStatement(const Source& source);
+  FallthroughStatement(ProgramID program_id, const Source& source);
   /// Move constructor
   FallthroughStatement(FallthroughStatement&&);
   ~FallthroughStatement() override;
diff --git a/src/ast/float_literal.cc b/src/ast/float_literal.cc
index 9454162..3b24f49 100644
--- a/src/ast/float_literal.cc
+++ b/src/ast/float_literal.cc
@@ -23,8 +23,11 @@
 namespace tint {
 namespace ast {
 
-FloatLiteral::FloatLiteral(const Source& source, type::Type* type, float value)
-    : Base(source, type), value_(value) {}
+FloatLiteral::FloatLiteral(ProgramID program_id,
+                           const Source& source,
+                           type::Type* type,
+                           float value)
+    : Base(program_id, source, type), value_(value) {}
 
 FloatLiteral::~FloatLiteral() = default;
 
diff --git a/src/ast/float_literal.h b/src/ast/float_literal.h
index f28ce97..46182bb 100644
--- a/src/ast/float_literal.h
+++ b/src/ast/float_literal.h
@@ -26,10 +26,14 @@
 class FloatLiteral : public Castable<FloatLiteral, Literal> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the input source
   /// @param type the type of the literal
   /// @param value the float literals value
-  FloatLiteral(const Source& source, type::Type* type, float value);
+  FloatLiteral(ProgramID program_id,
+               const Source& source,
+               type::Type* type,
+               float value);
   ~FloatLiteral() override;
 
   /// @returns the float literal value
diff --git a/src/ast/function.cc b/src/ast/function.cc
index d921e89..8efbb64 100644
--- a/src/ast/function.cc
+++ b/src/ast/function.cc
@@ -23,14 +23,15 @@
 namespace tint {
 namespace ast {
 
-Function::Function(const Source& source,
+Function::Function(ProgramID program_id,
+                   const Source& source,
                    Symbol symbol,
                    VariableList params,
                    type::Type* return_type,
                    BlockStatement* body,
                    DecorationList decorations,
                    DecorationList return_type_decorations)
-    : Base(source),
+    : Base(program_id, source),
       symbol_(symbol),
       params_(std::move(params)),
       return_type_(return_type),
diff --git a/src/ast/function.h b/src/ast/function.h
index eb856c3..0717a28 100644
--- a/src/ast/function.h
+++ b/src/ast/function.h
@@ -36,6 +36,7 @@
 class Function : public Castable<Function, Node> {
  public:
   /// Create a function
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the variable source
   /// @param symbol the function symbol
   /// @param params the function parameters
@@ -43,7 +44,8 @@
   /// @param body the function body
   /// @param decorations the function decorations
   /// @param return_type_decorations the return type decorations
-  Function(const Source& source,
+  Function(ProgramID program_id,
+           const Source& source,
            Symbol symbol,
            VariableList params,
            type::Type* return_type,
diff --git a/src/ast/group_decoration.cc b/src/ast/group_decoration.cc
index d531859..58c59f0 100644
--- a/src/ast/group_decoration.cc
+++ b/src/ast/group_decoration.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-GroupDecoration::GroupDecoration(const Source& source, uint32_t val)
-    : Base(source), value_(val) {}
+GroupDecoration::GroupDecoration(ProgramID program_id,
+                                 const Source& source,
+                                 uint32_t val)
+    : Base(program_id, source), value_(val) {}
 
 GroupDecoration::~GroupDecoration() = default;
 
diff --git a/src/ast/group_decoration.h b/src/ast/group_decoration.h
index b7482f0..3a2d718 100644
--- a/src/ast/group_decoration.h
+++ b/src/ast/group_decoration.h
@@ -24,9 +24,10 @@
 class GroupDecoration : public Castable<GroupDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param value the group value
   /// @param source the source of this decoration
-  GroupDecoration(const Source& source, uint32_t value);
+  GroupDecoration(ProgramID program_id, const Source& source, uint32_t value);
   ~GroupDecoration() override;
 
   /// @returns the group value
diff --git a/src/ast/identifier_expression.cc b/src/ast/identifier_expression.cc
index e996bac..95784e0 100644
--- a/src/ast/identifier_expression.cc
+++ b/src/ast/identifier_expression.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-IdentifierExpression::IdentifierExpression(const Source& source, Symbol sym)
-    : Base(source), sym_(sym) {
+IdentifierExpression::IdentifierExpression(ProgramID program_id,
+                                           const Source& source,
+                                           Symbol sym)
+    : Base(program_id, source), sym_(sym) {
   TINT_ASSERT(sym_.IsValid());
 }
 
diff --git a/src/ast/identifier_expression.h b/src/ast/identifier_expression.h
index 396ad33..888410d 100644
--- a/src/ast/identifier_expression.h
+++ b/src/ast/identifier_expression.h
@@ -25,9 +25,10 @@
 class IdentifierExpression : public Castable<IdentifierExpression, Expression> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source
   /// @param sym the symbol for the identifier
-  IdentifierExpression(const Source& source, Symbol sym);
+  IdentifierExpression(ProgramID program_id, const Source& source, Symbol sym);
   /// Move constructor
   IdentifierExpression(IdentifierExpression&&);
   ~IdentifierExpression() override;
diff --git a/src/ast/if_statement.cc b/src/ast/if_statement.cc
index 7a515db..3080ca4 100644
--- a/src/ast/if_statement.cc
+++ b/src/ast/if_statement.cc
@@ -21,11 +21,12 @@
 namespace tint {
 namespace ast {
 
-IfStatement::IfStatement(const Source& source,
+IfStatement::IfStatement(ProgramID program_id,
+                         const Source& source,
                          Expression* condition,
                          BlockStatement* body,
                          ElseStatementList else_stmts)
-    : Base(source),
+    : Base(program_id, source),
       condition_(condition),
       body_(body),
       else_statements_(std::move(else_stmts)) {
diff --git a/src/ast/if_statement.h b/src/ast/if_statement.h
index cf2f09a..5d79057 100644
--- a/src/ast/if_statement.h
+++ b/src/ast/if_statement.h
@@ -26,11 +26,13 @@
 class IfStatement : public Castable<IfStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source information
   /// @param condition the if condition
   /// @param body the if body
   /// @param else_stmts the else statements
-  IfStatement(const Source& source,
+  IfStatement(ProgramID program_id,
+              const Source& source,
               Expression* condition,
               BlockStatement* body,
               ElseStatementList else_stmts);
diff --git a/src/ast/int_literal.cc b/src/ast/int_literal.cc
index d915650..0b3db78 100644
--- a/src/ast/int_literal.cc
+++ b/src/ast/int_literal.cc
@@ -19,8 +19,11 @@
 namespace tint {
 namespace ast {
 
-IntLiteral::IntLiteral(const Source& source, type::Type* type, uint32_t value)
-    : Base(source, type), value_(value) {}
+IntLiteral::IntLiteral(ProgramID program_id,
+                       const Source& source,
+                       type::Type* type,
+                       uint32_t value)
+    : Base(program_id, source, type), value_(value) {}
 
 IntLiteral::~IntLiteral() = default;
 
diff --git a/src/ast/int_literal.h b/src/ast/int_literal.h
index c8f0d7d..2c2afeb 100644
--- a/src/ast/int_literal.h
+++ b/src/ast/int_literal.h
@@ -30,10 +30,14 @@
 
  protected:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the input source
   /// @param type the type of the literal
   /// @param value value of the literal
-  IntLiteral(const Source& source, type::Type* type, uint32_t value);
+  IntLiteral(ProgramID program_id,
+             const Source& source,
+             type::Type* type,
+             uint32_t value);
 
  private:
   uint32_t const value_;
diff --git a/src/ast/internal_decoration.cc b/src/ast/internal_decoration.cc
index d00d4b0..0913dea 100644
--- a/src/ast/internal_decoration.cc
+++ b/src/ast/internal_decoration.cc
@@ -19,7 +19,8 @@
 namespace tint {
 namespace ast {
 
-InternalDecoration::InternalDecoration() : Base(Source{}) {}
+InternalDecoration::InternalDecoration(ProgramID program_id)
+    : Base(program_id, Source{}) {}
 
 InternalDecoration::~InternalDecoration() = default;
 
diff --git a/src/ast/internal_decoration.h b/src/ast/internal_decoration.h
index ce242c6..fe3b361 100644
--- a/src/ast/internal_decoration.h
+++ b/src/ast/internal_decoration.h
@@ -29,7 +29,8 @@
 class InternalDecoration : public Castable<InternalDecoration, Decoration> {
  public:
   /// Constructor
-  InternalDecoration();
+  /// @param program_id the identifier of the program that owns this node
+  explicit InternalDecoration(ProgramID program_id);
 
   /// Destructor
   ~InternalDecoration() override;
diff --git a/src/ast/literal.cc b/src/ast/literal.cc
index 3d1b063..6f26682 100644
--- a/src/ast/literal.cc
+++ b/src/ast/literal.cc
@@ -19,8 +19,8 @@
 namespace tint {
 namespace ast {
 
-Literal::Literal(const Source& source, type::Type* type)
-    : Base(source), type_(type) {}
+Literal::Literal(ProgramID program_id, const Source& source, type::Type* type)
+    : Base(program_id, source), type_(type) {}
 
 Literal::~Literal() = default;
 
diff --git a/src/ast/literal.h b/src/ast/literal.h
index 70039c9..b26a9f5 100644
--- a/src/ast/literal.h
+++ b/src/ast/literal.h
@@ -47,9 +47,12 @@
 
  protected:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the input source
   /// @param type the type of the literal
-  explicit Literal(const Source& source, type::Type* type);
+  explicit Literal(ProgramID program_id,
+                   const Source& source,
+                   type::Type* type);
 
  private:
   type::Type* const type_;
diff --git a/src/ast/location_decoration.cc b/src/ast/location_decoration.cc
index d6e7f2f..e104791 100644
--- a/src/ast/location_decoration.cc
+++ b/src/ast/location_decoration.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-LocationDecoration::LocationDecoration(const Source& source, uint32_t val)
-    : Base(source), value_(val) {}
+LocationDecoration::LocationDecoration(ProgramID program_id,
+                                       const Source& source,
+                                       uint32_t val)
+    : Base(program_id, source), value_(val) {}
 
 LocationDecoration::~LocationDecoration() = default;
 
diff --git a/src/ast/location_decoration.h b/src/ast/location_decoration.h
index 4318199..c292e71 100644
--- a/src/ast/location_decoration.h
+++ b/src/ast/location_decoration.h
@@ -24,9 +24,12 @@
 class LocationDecoration : public Castable<LocationDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param value the location value
-  LocationDecoration(const Source& source, uint32_t value);
+  LocationDecoration(ProgramID program_id,
+                     const Source& source,
+                     uint32_t value);
   ~LocationDecoration() override;
 
   /// @returns the location value
diff --git a/src/ast/loop_statement.cc b/src/ast/loop_statement.cc
index 962db02..10e5208 100644
--- a/src/ast/loop_statement.cc
+++ b/src/ast/loop_statement.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-LoopStatement::LoopStatement(const Source& source,
+LoopStatement::LoopStatement(ProgramID program_id,
+                             const Source& source,
                              BlockStatement* body,
                              BlockStatement* continuing)
-    : Base(source), body_(body), continuing_(continuing) {
+    : Base(program_id, source), body_(body), continuing_(continuing) {
   TINT_ASSERT(body_);
 }
 
diff --git a/src/ast/loop_statement.h b/src/ast/loop_statement.h
index f2ee7ad..fc6da1f 100644
--- a/src/ast/loop_statement.h
+++ b/src/ast/loop_statement.h
@@ -24,10 +24,12 @@
 class LoopStatement : public Castable<LoopStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the loop statement source
   /// @param body the body statements
   /// @param continuing the continuing statements
-  LoopStatement(const Source& source,
+  LoopStatement(ProgramID program_id,
+                const Source& source,
                 BlockStatement* body,
                 BlockStatement* continuing);
   /// Move constructor
diff --git a/src/ast/member_accessor_expression.cc b/src/ast/member_accessor_expression.cc
index 6e8e9a9..862d6f0 100644
--- a/src/ast/member_accessor_expression.cc
+++ b/src/ast/member_accessor_expression.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-MemberAccessorExpression::MemberAccessorExpression(const Source& source,
+MemberAccessorExpression::MemberAccessorExpression(ProgramID program_id,
+                                                   const Source& source,
                                                    Expression* structure,
                                                    IdentifierExpression* member)
-    : Base(source), struct_(structure), member_(member) {
+    : Base(program_id, source), struct_(structure), member_(member) {
   TINT_ASSERT(structure);
   TINT_ASSERT(member);
 }
diff --git a/src/ast/member_accessor_expression.h b/src/ast/member_accessor_expression.h
index ac2c8bf..24209ac 100644
--- a/src/ast/member_accessor_expression.h
+++ b/src/ast/member_accessor_expression.h
@@ -25,10 +25,12 @@
     : public Castable<MemberAccessorExpression, Expression> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the member accessor expression source
   /// @param structure the structure
   /// @param member the member
-  MemberAccessorExpression(const Source& source,
+  MemberAccessorExpression(ProgramID program_id,
+                           const Source& source,
                            Expression* structure,
                            IdentifierExpression* member);
   /// Move constructor
diff --git a/src/ast/module.cc b/src/ast/module.cc
index 97651c3..cacc5ec 100644
--- a/src/ast/module.cc
+++ b/src/ast/module.cc
@@ -23,10 +23,13 @@
 namespace tint {
 namespace ast {
 
-Module::Module(const Source& source) : Base(source) {}
+Module::Module(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
 
-Module::Module(const Source& source, std::vector<Cloneable*> global_decls)
-    : Base(source), global_declarations_(std::move(global_decls)) {
+Module::Module(ProgramID program_id,
+               const Source& source,
+               std::vector<Cloneable*> global_decls)
+    : Base(program_id, source), global_declarations_(std::move(global_decls)) {
   for (auto* decl : global_declarations_) {
     if (decl == nullptr) {
       continue;
diff --git a/src/ast/module.h b/src/ast/module.h
index 4defcc8..efca69e 100644
--- a/src/ast/module.h
+++ b/src/ast/module.h
@@ -28,14 +28,18 @@
 class Module : public Castable<Module, Node> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of the module
-  explicit Module(const Source& source);
+  Module(ProgramID program_id, const Source& source);
 
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of the module
   /// @param global_decls the list of global types, functions, and variables, in
   /// the order they were declared in the source program
-  Module(const Source& source, std::vector<Cloneable*> global_decls);
+  Module(ProgramID program_id,
+         const Source& source,
+         std::vector<Cloneable*> global_decls);
 
   /// Destructor
   ~Module() override;
diff --git a/src/ast/node.cc b/src/ast/node.cc
index a29fabc..dce8c48 100644
--- a/src/ast/node.cc
+++ b/src/ast/node.cc
@@ -19,7 +19,8 @@
 namespace tint {
 namespace ast {
 
-Node::Node(const Source& source) : source_(source) {}
+Node::Node(ProgramID program_id, const Source& source)
+    : program_id_(program_id), source_(source) {}
 
 Node::Node(Node&&) = default;
 
diff --git a/src/ast/node.h b/src/ast/node.h
index 157f93e..827f1dc 100644
--- a/src/ast/node.h
+++ b/src/ast/node.h
@@ -18,6 +18,7 @@
 #include <string>
 
 #include "src/clone_context.h"
+#include "src/program_id.h"
 
 namespace tint {
 
@@ -37,6 +38,9 @@
  public:
   ~Node() override;
 
+  /// @returns the identifier of the program that owns this node
+  ProgramID program_id() const { return program_id_; }
+
   /// @returns the node source data
   const Source& source() const { return source_; }
 
@@ -55,8 +59,9 @@
 
  protected:
   /// Create a new node
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the input source for the node
-  explicit Node(const Source& source);
+  Node(ProgramID program_id, const Source& source);
   /// Move constructor
   Node(Node&&);
 
@@ -68,6 +73,7 @@
  private:
   Node(const Node&) = delete;
 
+  ProgramID const program_id_;
   Source const source_;
 };
 
diff --git a/src/ast/return_statement.cc b/src/ast/return_statement.cc
index 7c5e3a1..a38aac0 100644
--- a/src/ast/return_statement.cc
+++ b/src/ast/return_statement.cc
@@ -21,11 +21,13 @@
 namespace tint {
 namespace ast {
 
-ReturnStatement::ReturnStatement(const Source& source)
-    : Base(source), value_(nullptr) {}
+ReturnStatement::ReturnStatement(ProgramID program_id, const Source& source)
+    : Base(program_id, source), value_(nullptr) {}
 
-ReturnStatement::ReturnStatement(const Source& source, Expression* value)
-    : Base(source), value_(value) {}
+ReturnStatement::ReturnStatement(ProgramID program_id,
+                                 const Source& source,
+                                 Expression* value)
+    : Base(program_id, source), value_(value) {}
 
 ReturnStatement::ReturnStatement(ReturnStatement&&) = default;
 
diff --git a/src/ast/return_statement.h b/src/ast/return_statement.h
index 77f4805..fb06dd6 100644
--- a/src/ast/return_statement.h
+++ b/src/ast/return_statement.h
@@ -25,12 +25,17 @@
 class ReturnStatement : public Castable<ReturnStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source information
-  explicit ReturnStatement(const Source& source);
+  ReturnStatement(ProgramID program_id, const Source& source);
+
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the return statement source
   /// @param value the return value
-  ReturnStatement(const Source& source, Expression* value);
+  ReturnStatement(ProgramID program_id,
+                  const Source& source,
+                  Expression* value);
   /// Move constructor
   ReturnStatement(ReturnStatement&&);
   ~ReturnStatement() override;
diff --git a/src/ast/scalar_constructor_expression.cc b/src/ast/scalar_constructor_expression.cc
index 4f0ecce..01413aa 100644
--- a/src/ast/scalar_constructor_expression.cc
+++ b/src/ast/scalar_constructor_expression.cc
@@ -21,9 +21,10 @@
 namespace tint {
 namespace ast {
 
-ScalarConstructorExpression::ScalarConstructorExpression(const Source& source,
+ScalarConstructorExpression::ScalarConstructorExpression(ProgramID program_id,
+                                                         const Source& source,
                                                          Literal* literal)
-    : Base(source), literal_(literal) {
+    : Base(program_id, source), literal_(literal) {
   TINT_ASSERT(literal);
 }
 
diff --git a/src/ast/scalar_constructor_expression.h b/src/ast/scalar_constructor_expression.h
index 3b56357..4b19af2 100644
--- a/src/ast/scalar_constructor_expression.h
+++ b/src/ast/scalar_constructor_expression.h
@@ -26,9 +26,12 @@
     : public Castable<ScalarConstructorExpression, ConstructorExpression> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the constructor source
   /// @param literal the const literal
-  ScalarConstructorExpression(const Source& source, Literal* literal);
+  ScalarConstructorExpression(ProgramID program_id,
+                              const Source& source,
+                              Literal* literal);
   /// Move constructor
   ScalarConstructorExpression(ScalarConstructorExpression&&);
   ~ScalarConstructorExpression() override;
diff --git a/src/ast/sint_literal.cc b/src/ast/sint_literal.cc
index 69fee9e..8ceec65 100644
--- a/src/ast/sint_literal.cc
+++ b/src/ast/sint_literal.cc
@@ -21,8 +21,11 @@
 namespace tint {
 namespace ast {
 
-SintLiteral::SintLiteral(const Source& source, type::Type* type, int32_t value)
-    : Base(source, type, static_cast<uint32_t>(value)) {}
+SintLiteral::SintLiteral(ProgramID program_id,
+                         const Source& source,
+                         type::Type* type,
+                         int32_t value)
+    : Base(program_id, source, type, static_cast<uint32_t>(value)) {}
 
 SintLiteral::~SintLiteral() = default;
 
diff --git a/src/ast/sint_literal.h b/src/ast/sint_literal.h
index 08b87de..d951150 100644
--- a/src/ast/sint_literal.h
+++ b/src/ast/sint_literal.h
@@ -26,10 +26,14 @@
 class SintLiteral : public Castable<SintLiteral, IntLiteral> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the input source
   /// @param type the type
   /// @param value the signed int literals value
-  SintLiteral(const Source& source, type::Type* type, int32_t value);
+  SintLiteral(ProgramID program_id,
+              const Source& source,
+              type::Type* type,
+              int32_t value);
   ~SintLiteral() override;
 
   /// @returns the int literal value
diff --git a/src/ast/stage_decoration.cc b/src/ast/stage_decoration.cc
index 77720c5..129068f 100644
--- a/src/ast/stage_decoration.cc
+++ b/src/ast/stage_decoration.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-StageDecoration::StageDecoration(const Source& source, PipelineStage stage)
-    : Base(source), stage_(stage) {}
+StageDecoration::StageDecoration(ProgramID program_id,
+                                 const Source& source,
+                                 PipelineStage stage)
+    : Base(program_id, source), stage_(stage) {}
 
 StageDecoration::~StageDecoration() = default;
 
diff --git a/src/ast/stage_decoration.h b/src/ast/stage_decoration.h
index 9509501..b392c5b 100644
--- a/src/ast/stage_decoration.h
+++ b/src/ast/stage_decoration.h
@@ -25,9 +25,12 @@
 class StageDecoration : public Castable<StageDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param stage the pipeline stage
   /// @param source the source of this decoration
-  StageDecoration(const Source& source, PipelineStage stage);
+  StageDecoration(ProgramID program_id,
+                  const Source& source,
+                  PipelineStage stage);
   ~StageDecoration() override;
 
   /// @returns the stage
diff --git a/src/ast/statement.cc b/src/ast/statement.cc
index 0283abe..f4618a0 100644
--- a/src/ast/statement.cc
+++ b/src/ast/statement.cc
@@ -31,7 +31,8 @@
 namespace tint {
 namespace ast {
 
-Statement::Statement(const Source& source) : Base(source) {}
+Statement::Statement(ProgramID program_id, const Source& source)
+    : Base(program_id, source) {}
 
 Statement::Statement(Statement&&) = default;
 
diff --git a/src/ast/statement.h b/src/ast/statement.h
index 53a6fd0..b371a1e 100644
--- a/src/ast/statement.h
+++ b/src/ast/statement.h
@@ -32,8 +32,9 @@
 
  protected:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of the expression
-  explicit Statement(const Source& source);
+  Statement(ProgramID program_id, const Source& source);
   /// Move constructor
   Statement(Statement&&);
 
diff --git a/src/ast/stride_decoration.cc b/src/ast/stride_decoration.cc
index 6cf0c8f..26ddda9 100644
--- a/src/ast/stride_decoration.cc
+++ b/src/ast/stride_decoration.cc
@@ -21,8 +21,10 @@
 namespace tint {
 namespace ast {
 
-StrideDecoration::StrideDecoration(const Source& source, uint32_t stride)
-    : Base(source), stride_(stride) {}
+StrideDecoration::StrideDecoration(ProgramID program_id,
+                                   const Source& source,
+                                   uint32_t stride)
+    : Base(program_id, source), stride_(stride) {}
 
 StrideDecoration::~StrideDecoration() = default;
 
diff --git a/src/ast/stride_decoration.h b/src/ast/stride_decoration.h
index b656c34..be147d1 100644
--- a/src/ast/stride_decoration.h
+++ b/src/ast/stride_decoration.h
@@ -24,9 +24,10 @@
 class StrideDecoration : public Castable<StrideDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param stride the stride value
   /// @param source the source of this decoration
-  StrideDecoration(const Source& source, uint32_t stride);
+  StrideDecoration(ProgramID program_id, const Source& source, uint32_t stride);
   ~StrideDecoration() override;
 
   /// @returns the stride value
diff --git a/src/ast/struct.cc b/src/ast/struct.cc
index e38d350..eb6d6c2 100644
--- a/src/ast/struct.cc
+++ b/src/ast/struct.cc
@@ -22,10 +22,11 @@
 namespace tint {
 namespace ast {
 
-Struct::Struct(const Source& source,
+Struct::Struct(ProgramID program_id,
+               const Source& source,
                StructMemberList members,
                DecorationList decorations)
-    : Base(source),
+    : Base(program_id, source),
       members_(std::move(members)),
       decorations_(std::move(decorations)) {
   for (auto* mem : members_) {
diff --git a/src/ast/struct.h b/src/ast/struct.h
index aa581b6..a497f63 100644
--- a/src/ast/struct.h
+++ b/src/ast/struct.h
@@ -27,10 +27,12 @@
 class Struct : public Castable<Struct, Node> {
  public:
   /// Create a new struct statement
+  /// @param program_id the identifier of the program that owns this node
   /// @param source The input source for the import statement
   /// @param members The struct members
   /// @param decorations The struct decorations
-  Struct(const Source& source,
+  Struct(ProgramID program_id,
+         const Source& source,
          StructMemberList members,
          DecorationList decorations);
   /// Move constructor
diff --git a/src/ast/struct_block_decoration.cc b/src/ast/struct_block_decoration.cc
index dfc3b0b..dc23bfc 100644
--- a/src/ast/struct_block_decoration.cc
+++ b/src/ast/struct_block_decoration.cc
@@ -21,8 +21,9 @@
 namespace tint {
 namespace ast {
 
-StructBlockDecoration::StructBlockDecoration(const Source& source)
-    : Base(source) {}
+StructBlockDecoration::StructBlockDecoration(ProgramID program_id,
+                                             const Source& source)
+    : Base(program_id, source) {}
 
 StructBlockDecoration::~StructBlockDecoration() = default;
 
diff --git a/src/ast/struct_block_decoration.h b/src/ast/struct_block_decoration.h
index ca8a51e..ec3ecb0 100644
--- a/src/ast/struct_block_decoration.h
+++ b/src/ast/struct_block_decoration.h
@@ -27,8 +27,9 @@
     : public Castable<StructBlockDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
-  explicit StructBlockDecoration(const Source& source);
+  StructBlockDecoration(ProgramID program_id, const Source& source);
   ~StructBlockDecoration() override;
 
   /// Outputs the decoration to the given stream
diff --git a/src/ast/struct_member.cc b/src/ast/struct_member.cc
index 70b803f..a36326e 100644
--- a/src/ast/struct_member.cc
+++ b/src/ast/struct_member.cc
@@ -21,11 +21,12 @@
 namespace tint {
 namespace ast {
 
-StructMember::StructMember(const Source& source,
+StructMember::StructMember(ProgramID program_id,
+                           const Source& source,
                            const Symbol& sym,
                            type::Type* type,
                            DecorationList decorations)
-    : Base(source),
+    : Base(program_id, source),
       symbol_(sym),
       type_(type),
       decorations_(std::move(decorations)) {
diff --git a/src/ast/struct_member.h b/src/ast/struct_member.h
index c02cc7e..6ecf823 100644
--- a/src/ast/struct_member.h
+++ b/src/ast/struct_member.h
@@ -27,11 +27,13 @@
 class StructMember : public Castable<StructMember, Node> {
  public:
   /// Create a new struct member statement
+  /// @param program_id the identifier of the program that owns this node
   /// @param source The input source for the struct member statement
   /// @param sym The struct member symbol
   /// @param type The struct member type
   /// @param decorations The struct member decorations
-  StructMember(const Source& source,
+  StructMember(ProgramID program_id,
+               const Source& source,
                const Symbol& sym,
                type::Type* type,
                DecorationList decorations);
diff --git a/src/ast/struct_member_align_decoration.cc b/src/ast/struct_member_align_decoration.cc
index 03a353c..d45911a 100644
--- a/src/ast/struct_member_align_decoration.cc
+++ b/src/ast/struct_member_align_decoration.cc
@@ -22,9 +22,10 @@
 namespace tint {
 namespace ast {
 
-StructMemberAlignDecoration::StructMemberAlignDecoration(const Source& source,
+StructMemberAlignDecoration::StructMemberAlignDecoration(ProgramID program_id,
+                                                         const Source& source,
                                                          uint32_t align)
-    : Base(source), align_(align) {}
+    : Base(program_id, source), align_(align) {}
 
 StructMemberAlignDecoration::~StructMemberAlignDecoration() = default;
 
diff --git a/src/ast/struct_member_align_decoration.h b/src/ast/struct_member_align_decoration.h
index e408e32..dfa32d2 100644
--- a/src/ast/struct_member_align_decoration.h
+++ b/src/ast/struct_member_align_decoration.h
@@ -27,9 +27,12 @@
     : public Castable<StructMemberAlignDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param align the align value
-  StructMemberAlignDecoration(const Source& source, uint32_t align);
+  StructMemberAlignDecoration(ProgramID program_id,
+                              const Source& source,
+                              uint32_t align);
   ~StructMemberAlignDecoration() override;
 
   /// @returns the align value
diff --git a/src/ast/struct_member_offset_decoration.cc b/src/ast/struct_member_offset_decoration.cc
index 4f9f736..6f09870 100644
--- a/src/ast/struct_member_offset_decoration.cc
+++ b/src/ast/struct_member_offset_decoration.cc
@@ -21,9 +21,10 @@
 namespace tint {
 namespace ast {
 
-StructMemberOffsetDecoration::StructMemberOffsetDecoration(const Source& source,
+StructMemberOffsetDecoration::StructMemberOffsetDecoration(ProgramID program_id,
+                                                           const Source& source,
                                                            uint32_t offset)
-    : Base(source), offset_(offset) {}
+    : Base(program_id, source), offset_(offset) {}
 
 StructMemberOffsetDecoration::~StructMemberOffsetDecoration() = default;
 
diff --git a/src/ast/struct_member_offset_decoration.h b/src/ast/struct_member_offset_decoration.h
index b65b784..61c1caf 100644
--- a/src/ast/struct_member_offset_decoration.h
+++ b/src/ast/struct_member_offset_decoration.h
@@ -34,9 +34,12 @@
     : public Castable<StructMemberOffsetDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param offset the offset value
-  StructMemberOffsetDecoration(const Source& source, uint32_t offset);
+  StructMemberOffsetDecoration(ProgramID program_id,
+                               const Source& source,
+                               uint32_t offset);
   ~StructMemberOffsetDecoration() override;
 
   /// @returns the offset value
diff --git a/src/ast/struct_member_size_decoration.cc b/src/ast/struct_member_size_decoration.cc
index 54e536a..ec4ae77 100644
--- a/src/ast/struct_member_size_decoration.cc
+++ b/src/ast/struct_member_size_decoration.cc
@@ -22,9 +22,10 @@
 namespace tint {
 namespace ast {
 
-StructMemberSizeDecoration::StructMemberSizeDecoration(const Source& source,
+StructMemberSizeDecoration::StructMemberSizeDecoration(ProgramID program_id,
+                                                       const Source& source,
                                                        uint32_t size)
-    : Base(source), size_(size) {}
+    : Base(program_id, source), size_(size) {}
 
 StructMemberSizeDecoration::~StructMemberSizeDecoration() = default;
 
diff --git a/src/ast/struct_member_size_decoration.h b/src/ast/struct_member_size_decoration.h
index f99fd50..5d3be8f 100644
--- a/src/ast/struct_member_size_decoration.h
+++ b/src/ast/struct_member_size_decoration.h
@@ -27,9 +27,12 @@
     : public Castable<StructMemberSizeDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param size the size value
-  StructMemberSizeDecoration(const Source& source, uint32_t size);
+  StructMemberSizeDecoration(ProgramID program_id,
+                             const Source& source,
+                             uint32_t size);
   ~StructMemberSizeDecoration() override;
 
   /// @returns the size value
diff --git a/src/ast/switch_statement.cc b/src/ast/switch_statement.cc
index c413dd2..136236b 100644
--- a/src/ast/switch_statement.cc
+++ b/src/ast/switch_statement.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-SwitchStatement::SwitchStatement(const Source& source,
+SwitchStatement::SwitchStatement(ProgramID program_id,
+                                 const Source& source,
                                  Expression* condition,
                                  CaseStatementList body)
-    : Base(source), condition_(condition), body_(body) {
+    : Base(program_id, source), condition_(condition), body_(body) {
   TINT_ASSERT(condition_);
   for (auto* stmt : body_) {
     TINT_ASSERT(stmt);
diff --git a/src/ast/switch_statement.h b/src/ast/switch_statement.h
index 0f78635..16c6622 100644
--- a/src/ast/switch_statement.h
+++ b/src/ast/switch_statement.h
@@ -25,10 +25,12 @@
 class SwitchStatement : public Castable<SwitchStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source information
   /// @param condition the switch condition
   /// @param body the switch body
-  SwitchStatement(const Source& source,
+  SwitchStatement(ProgramID program_id,
+                  const Source& source,
                   Expression* condition,
                   CaseStatementList body);
   /// Move constructor
diff --git a/src/ast/type_constructor_expression.cc b/src/ast/type_constructor_expression.cc
index edf78fa..2cf6ea3 100644
--- a/src/ast/type_constructor_expression.cc
+++ b/src/ast/type_constructor_expression.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-TypeConstructorExpression::TypeConstructorExpression(const Source& source,
+TypeConstructorExpression::TypeConstructorExpression(ProgramID program_id,
+                                                     const Source& source,
                                                      type::Type* type,
                                                      ExpressionList values)
-    : Base(source), type_(type), values_(std::move(values)) {
+    : Base(program_id, source), type_(type), values_(std::move(values)) {
   TINT_ASSERT(type);
   for (auto* val : values_) {
     TINT_ASSERT(val);
diff --git a/src/ast/type_constructor_expression.h b/src/ast/type_constructor_expression.h
index 9381665..37629da 100644
--- a/src/ast/type_constructor_expression.h
+++ b/src/ast/type_constructor_expression.h
@@ -27,10 +27,12 @@
     : public Castable<TypeConstructorExpression, ConstructorExpression> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the constructor source
   /// @param type the type
   /// @param values the constructor values
-  TypeConstructorExpression(const Source& source,
+  TypeConstructorExpression(ProgramID program_id,
+                            const Source& source,
                             type::Type* type,
                             ExpressionList values);
   /// Move constructor
diff --git a/src/ast/uint_literal.cc b/src/ast/uint_literal.cc
index 5d981cc..eef8e38 100644
--- a/src/ast/uint_literal.cc
+++ b/src/ast/uint_literal.cc
@@ -21,8 +21,11 @@
 namespace tint {
 namespace ast {
 
-UintLiteral::UintLiteral(const Source& source, type::Type* type, uint32_t value)
-    : Base(source, type, value) {}
+UintLiteral::UintLiteral(ProgramID program_id,
+                         const Source& source,
+                         type::Type* type,
+                         uint32_t value)
+    : Base(program_id, source, type, value) {}
 
 UintLiteral::~UintLiteral() = default;
 
diff --git a/src/ast/uint_literal.h b/src/ast/uint_literal.h
index 5862b13..141239f 100644
--- a/src/ast/uint_literal.h
+++ b/src/ast/uint_literal.h
@@ -26,10 +26,14 @@
 class UintLiteral : public Castable<UintLiteral, IntLiteral> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the input source
   /// @param type the type of the literal
   /// @param value the uint literals value
-  UintLiteral(const Source& source, type::Type* type, uint32_t value);
+  UintLiteral(ProgramID program_id,
+              const Source& source,
+              type::Type* type,
+              uint32_t value);
   ~UintLiteral() override;
 
   /// @returns the uint literal value
diff --git a/src/ast/unary_op_expression.cc b/src/ast/unary_op_expression.cc
index 166d3f0..5f97221 100644
--- a/src/ast/unary_op_expression.cc
+++ b/src/ast/unary_op_expression.cc
@@ -21,10 +21,11 @@
 namespace tint {
 namespace ast {
 
-UnaryOpExpression::UnaryOpExpression(const Source& source,
+UnaryOpExpression::UnaryOpExpression(ProgramID program_id,
+                                     const Source& source,
                                      UnaryOp op,
                                      Expression* expr)
-    : Base(source), op_(op), expr_(expr) {
+    : Base(program_id, source), op_(op), expr_(expr) {
   TINT_ASSERT(expr_);
 }
 
diff --git a/src/ast/unary_op_expression.h b/src/ast/unary_op_expression.h
index 91cd8ee..a9e2d6e 100644
--- a/src/ast/unary_op_expression.h
+++ b/src/ast/unary_op_expression.h
@@ -25,10 +25,14 @@
 class UnaryOpExpression : public Castable<UnaryOpExpression, Expression> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the unary op expression source
   /// @param op the op
   /// @param expr the expr
-  UnaryOpExpression(const Source& source, UnaryOp op, Expression* expr);
+  UnaryOpExpression(ProgramID program_id,
+                    const Source& source,
+                    UnaryOp op,
+                    Expression* expr);
   /// Move constructor
   UnaryOpExpression(UnaryOpExpression&&);
   ~UnaryOpExpression() override;
diff --git a/src/ast/variable.cc b/src/ast/variable.cc
index 24308b5..13a50c8 100644
--- a/src/ast/variable.cc
+++ b/src/ast/variable.cc
@@ -23,14 +23,15 @@
 namespace tint {
 namespace ast {
 
-Variable::Variable(const Source& source,
+Variable::Variable(ProgramID program_id,
+                   const Source& source,
                    const Symbol& sym,
                    StorageClass declared_storage_class,
                    type::Type* declared_type,
                    bool is_const,
                    Expression* constructor,
                    DecorationList decorations)
-    : Base(source),
+    : Base(program_id, source),
       symbol_(sym),
       declared_type_(declared_type),
       is_const_(is_const),
diff --git a/src/ast/variable.h b/src/ast/variable.h
index a512d01..6caeee8 100644
--- a/src/ast/variable.h
+++ b/src/ast/variable.h
@@ -91,6 +91,7 @@
   };
 
   /// Create a variable
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the variable source
   /// @param sym the variable symbol
   /// @param declared_storage_class the declared storage class
@@ -98,7 +99,8 @@
   /// @param is_const true if the variable is const
   /// @param constructor the constructor expression
   /// @param decorations the variable decorations
-  Variable(const Source& source,
+  Variable(ProgramID program_id,
+           const Source& source,
            const Symbol& sym,
            StorageClass declared_storage_class,
            type::Type* declared_type,
diff --git a/src/ast/variable_decl_statement.cc b/src/ast/variable_decl_statement.cc
index 9df4007..f082e46 100644
--- a/src/ast/variable_decl_statement.cc
+++ b/src/ast/variable_decl_statement.cc
@@ -21,9 +21,10 @@
 namespace tint {
 namespace ast {
 
-VariableDeclStatement::VariableDeclStatement(const Source& source,
+VariableDeclStatement::VariableDeclStatement(ProgramID program_id,
+                                             const Source& source,
                                              Variable* variable)
-    : Base(source), variable_(variable) {
+    : Base(program_id, source), variable_(variable) {
   TINT_ASSERT(variable_);
 }
 
diff --git a/src/ast/variable_decl_statement.h b/src/ast/variable_decl_statement.h
index 698d19a..4cfdfa9 100644
--- a/src/ast/variable_decl_statement.h
+++ b/src/ast/variable_decl_statement.h
@@ -26,9 +26,12 @@
     : public Castable<VariableDeclStatement, Statement> {
  public:
   /// Constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the variable statement source
   /// @param variable the variable
-  VariableDeclStatement(const Source& source, Variable* variable);
+  VariableDeclStatement(ProgramID program_id,
+                        const Source& source,
+                        Variable* variable);
   /// Move constructor
   VariableDeclStatement(VariableDeclStatement&&);
   ~VariableDeclStatement() override;
diff --git a/src/ast/workgroup_decoration.cc b/src/ast/workgroup_decoration.cc
index 9e91575..85e6dbe 100644
--- a/src/ast/workgroup_decoration.cc
+++ b/src/ast/workgroup_decoration.cc
@@ -21,19 +21,23 @@
 namespace tint {
 namespace ast {
 
-WorkgroupDecoration::WorkgroupDecoration(const Source& source, uint32_t x)
-    : WorkgroupDecoration(source, x, 1, 1) {}
+WorkgroupDecoration::WorkgroupDecoration(ProgramID program_id,
+                                         const Source& source,
+                                         uint32_t x)
+    : WorkgroupDecoration(program_id, source, x, 1, 1) {}
 
-WorkgroupDecoration::WorkgroupDecoration(const Source& source,
+WorkgroupDecoration::WorkgroupDecoration(ProgramID program_id,
+                                         const Source& source,
                                          uint32_t x,
                                          uint32_t y)
-    : WorkgroupDecoration(source, x, y, 1) {}
+    : WorkgroupDecoration(program_id, source, x, y, 1) {}
 
-WorkgroupDecoration::WorkgroupDecoration(const Source& source,
+WorkgroupDecoration::WorkgroupDecoration(ProgramID program_id,
+                                         const Source& source,
                                          uint32_t x,
                                          uint32_t y,
                                          uint32_t z)
-    : Base(source), x_(x), y_(y), z_(z) {}
+    : Base(program_id, source), x_(x), y_(y), z_(z) {}
 
 WorkgroupDecoration::~WorkgroupDecoration() = default;
 
diff --git a/src/ast/workgroup_decoration.h b/src/ast/workgroup_decoration.h
index 19c8dd1..ae52f15 100644
--- a/src/ast/workgroup_decoration.h
+++ b/src/ast/workgroup_decoration.h
@@ -26,20 +26,30 @@
 class WorkgroupDecoration : public Castable<WorkgroupDecoration, Decoration> {
  public:
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param x the workgroup x dimension size
-  WorkgroupDecoration(const Source& source, uint32_t x);
+  WorkgroupDecoration(ProgramID program_id, const Source& source, uint32_t x);
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param x the workgroup x dimension size
   /// @param y the workgroup x dimension size
-  WorkgroupDecoration(const Source& source, uint32_t x, uint32_t y);
+  WorkgroupDecoration(ProgramID program_id,
+                      const Source& source,
+                      uint32_t x,
+                      uint32_t y);
   /// constructor
+  /// @param program_id the identifier of the program that owns this node
   /// @param source the source of this decoration
   /// @param x the workgroup x dimension size
   /// @param y the workgroup x dimension size
   /// @param z the workgroup x dimension size
-  WorkgroupDecoration(const Source& source, uint32_t x, uint32_t y, uint32_t z);
+  WorkgroupDecoration(ProgramID program_id,
+                      const Source& source,
+                      uint32_t x,
+                      uint32_t y,
+                      uint32_t z);
   ~WorkgroupDecoration() override;
 
   /// @returns the workgroup dimensions
diff --git a/src/clone_context_test.cc b/src/clone_context_test.cc
index 488a339..ea6f902 100644
--- a/src/clone_context_test.cc
+++ b/src/clone_context_test.cc
@@ -21,7 +21,8 @@
 namespace {
 
 struct Node : public Castable<Node, ast::Node> {
-  explicit Node(const Source& source, Symbol n) : Base(source), name(n) {}
+  Node(ProgramID program_id, const Source& source, Symbol n)
+      : Base(program_id, source), name(n) {}
 
   Symbol name;
   Node* a = nullptr;
@@ -42,14 +43,17 @@
 };
 
 struct Replaceable : public Castable<Replaceable, Node> {
-  explicit Replaceable(const Source& source, Symbol n) : Base(source, n) {}
+  Replaceable(ProgramID program_id, const Source& source, Symbol n)
+      : Base(program_id, source, n) {}
 };
 struct Replacement : public Castable<Replacement, Replaceable> {
-  explicit Replacement(const Source& source, Symbol n) : Base(source, n) {}
+  Replacement(ProgramID program_id, const Source& source, Symbol n)
+      : Base(program_id, source, n) {}
 };
 
 struct NotANode : public Castable<NotANode, ast::Node> {
-  explicit NotANode(const Source& source) : Base(source) {}
+  NotANode(ProgramID program_id, const Source& source)
+      : Base(program_id, source) {}
 
   NotANode* Clone(CloneContext* ctx) const override {
     return ctx->dst->create<NotANode>();
diff --git a/src/program.cc b/src/program.cc
index 6f98099..77f6775 100644
--- a/src/program.cc
+++ b/src/program.cc
@@ -25,7 +25,8 @@
 Program::Program() = default;
 
 Program::Program(Program&& program)
-    : types_(std::move(program.types_)),
+    : id_(std::move(program.id_)),
+      types_(std::move(program.types_)),
       ast_nodes_(std::move(program.ast_nodes_)),
       sem_nodes_(std::move(program.sem_nodes_)),
       ast_(std::move(program.ast_)),
@@ -38,6 +39,8 @@
 }
 
 Program::Program(ProgramBuilder&& builder) {
+  id_ = builder.ID();
+
   is_valid_ = builder.IsValid();
   if (builder.ResolveOnBuild() && builder.IsValid()) {
     resolver::Resolver resolver(&builder);
@@ -48,12 +51,10 @@
   }
 
   // The above must be called *before* the calls to std::move() below
-
   types_ = std::move(builder.Types());
   ast_nodes_ = std::move(builder.ASTNodes());
   sem_nodes_ = std::move(builder.SemNodes());
-  ast_ = ast_nodes_.Create<ast::Module>(
-      Source{}, std::move(builder.AST().GlobalDeclarations()));
+  ast_ = &builder.AST();  // ast::Module is actually a heap allocation.
   sem_ = std::move(builder.Sem());
   symbols_ = std::move(builder.Symbols());
   diagnostics_.add(std::move(builder.Diagnostics()));
@@ -72,6 +73,7 @@
 Program& Program::operator=(Program&& program) {
   program.AssertNotMoved();
   program.moved_ = true;
+  id_ = std::move(program.id_);
   types_ = std::move(program.types_);
   ast_nodes_ = std::move(program.ast_nodes_);
   sem_nodes_ = std::move(program.sem_nodes_);
diff --git a/src/program.h b/src/program.h
index efb0d35..27cb929 100644
--- a/src/program.h
+++ b/src/program.h
@@ -18,6 +18,7 @@
 #include <string>
 
 #include "src/ast/function.h"
+#include "src/program_id.h"
 #include "src/semantic/info.h"
 #include "src/symbol_table.h"
 #include "src/type/type_manager.h"
@@ -61,6 +62,9 @@
   /// @return this Program
   Program& operator=(Program&& rhs);
 
+  /// @returns the unique identifier for this program
+  ProgramID ID() const { return id_; }
+
   /// @returns a reference to the program's types
   const type::Manager& Types() const {
     AssertNotMoved();
@@ -155,6 +159,7 @@
   /// Asserts that the program has not been moved.
   void AssertNotMoved() const;
 
+  ProgramID id_;
   type::Manager types_;
   ASTNodeAllocator ast_nodes_;
   SemNodeAllocator sem_nodes_;
diff --git a/src/program_builder.cc b/src/program_builder.cc
index baf39ca..034882e 100644
--- a/src/program_builder.cc
+++ b/src/program_builder.cc
@@ -24,10 +24,12 @@
 namespace tint {
 
 ProgramBuilder::ProgramBuilder()
-    : ast_(ast_nodes_.Create<ast::Module>(Source{})) {}
+    : id_(ProgramID::New()),
+      ast_(ast_nodes_.Create<ast::Module>(id_, Source{})) {}
 
 ProgramBuilder::ProgramBuilder(ProgramBuilder&& rhs)
-    : types_(std::move(rhs.types_)),
+    : id_(std::move(rhs.id_)),
+      types_(std::move(rhs.types_)),
       ast_nodes_(std::move(rhs.ast_nodes_)),
       sem_nodes_(std::move(rhs.sem_nodes_)),
       ast_(rhs.ast_),
@@ -41,6 +43,7 @@
 ProgramBuilder& ProgramBuilder::operator=(ProgramBuilder&& rhs) {
   rhs.MarkAsMoved();
   AssertNotMoved();
+  id_ = std::move(rhs.id_);
   types_ = std::move(rhs.types_);
   ast_nodes_ = std::move(rhs.ast_nodes_);
   sem_nodes_ = std::move(rhs.sem_nodes_);
@@ -52,6 +55,7 @@
 
 ProgramBuilder ProgramBuilder::Wrap(const Program* program) {
   ProgramBuilder builder;
+  builder.id_ = program->ID();
   builder.types_ = type::Manager::Wrap(program->Types());
   builder.ast_ = builder.create<ast::Module>(
       program->AST().source(), program->AST().GlobalDeclarations());
diff --git a/src/program_builder.h b/src/program_builder.h
index fb261cc..716c9f9 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -42,6 +42,7 @@
 #include "src/ast/uint_literal.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/program.h"
+#include "src/program_id.h"
 #include "src/type/alias_type.h"
 #include "src/type/array_type.h"
 #include "src/type/bool_type.h"
@@ -118,6 +119,9 @@
   /// @return the ProgramBuilder that wraps `program`
   static ProgramBuilder Wrap(const Program* program);
 
+  /// @returns the unique identifier for this program
+  ProgramID ID() const { return id_; }
+
   /// @returns a reference to the program's types
   type::Manager& Types() {
     AssertNotMoved();
@@ -237,7 +241,7 @@
   traits::EnableIfIsType<T, ast::Node>* create(const Source& source,
                                                ARGS&&... args) {
     AssertNotMoved();
-    return ast_nodes_.Create<T>(source, std::forward<ARGS>(args)...);
+    return ast_nodes_.Create<T>(id_, source, std::forward<ARGS>(args)...);
   }
 
   /// Creates a new ast::Node owned by the ProgramBuilder, injecting the current
@@ -249,7 +253,7 @@
   template <typename T>
   traits::EnableIfIsType<T, ast::Node>* create() {
     AssertNotMoved();
-    return ast_nodes_.Create<T>(source_);
+    return ast_nodes_.Create<T>(id_, source_);
   }
 
   /// Creates a new ast::Node owned by the ProgramBuilder, injecting the current
@@ -267,7 +271,7 @@
                    T>*
   create(ARG0&& arg0, ARGS&&... args) {
     AssertNotMoved();
-    return ast_nodes_.Create<T>(source_, std::forward<ARG0>(arg0),
+    return ast_nodes_.Create<T>(id_, source_, std::forward<ARG0>(arg0),
                                 std::forward<ARGS>(args)...);
   }
 
@@ -1425,6 +1429,7 @@
   void AssertNotMoved() const;
 
  private:
+  ProgramID id_;
   type::Manager types_;
   ASTNodeAllocator ast_nodes_;
   SemNodeAllocator sem_nodes_;
diff --git a/src/program_builder_test.cc b/src/program_builder_test.cc
index 828a9e2..bfa9159 100644
--- a/src/program_builder_test.cc
+++ b/src/program_builder_test.cc
@@ -21,6 +21,15 @@
 
 using ProgramBuilderTest = testing::Test;
 
+TEST_F(ProgramBuilderTest, IDsAreUnique) {
+  Program program_a(ProgramBuilder{});
+  Program program_b(ProgramBuilder{});
+  Program program_c(ProgramBuilder{});
+  EXPECT_NE(program_a.ID(), program_b.ID());
+  EXPECT_NE(program_b.ID(), program_c.ID());
+  EXPECT_NE(program_c.ID(), program_a.ID());
+}
+
 TEST_F(ProgramBuilderTest, WrapDoesntAffectInner) {
   Program inner([] {
     ProgramBuilder builder;
diff --git a/src/program_id.cc b/src/program_id.cc
new file mode 100644
index 0000000..c27ac45
--- /dev/null
+++ b/src/program_id.cc
@@ -0,0 +1,35 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/program_id.h"
+
+#include <atomic>
+
+namespace tint {
+
+namespace {
+
+std::atomic<uint32_t> next_program_id{1};
+
+}  // namespace
+
+ProgramID::ProgramID() = default;
+
+ProgramID::ProgramID(uint32_t id) : val(id) {}
+
+ProgramID ProgramID::New() {
+  return ProgramID(next_program_id++);
+}
+
+}  // namespace tint
diff --git a/src/program_id.h b/src/program_id.h
new file mode 100644
index 0000000..9b7240a
--- /dev/null
+++ b/src/program_id.h
@@ -0,0 +1,52 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_PROGRAM_ID_H_
+#define SRC_PROGRAM_ID_H_
+
+#include <stdint.h>
+
+namespace tint {
+
+/// A ProgramID is a unique identifier of a Program.
+/// ProgramID can be used to ensure that objects referenced by the Program are
+/// owned exclusively by that Program and have accidentally not leaked from
+/// another Program.
+class ProgramID {
+ public:
+  /// Constructor
+  ProgramID();
+
+  /// @returns a new ProgramID
+  static ProgramID New();
+
+  /// Equality operator
+  /// @param rhs the other ProgramID
+  /// @returns true if the ProgramIDs are equal
+  bool operator==(const ProgramID& rhs) const { return val == rhs.val; }
+
+  /// Inequality operator
+  /// @param rhs the other ProgramID
+  /// @returns true if the ProgramIDs are not equal
+  bool operator!=(const ProgramID& rhs) const { return val != rhs.val; }
+
+ private:
+  explicit ProgramID(uint32_t);
+
+  uint32_t val = 0;
+};
+
+}  // namespace tint
+
+#endif  // SRC_PROGRAM_ID_H_
diff --git a/src/program_test.cc b/src/program_test.cc
index 700923c..9b3e38e 100644
--- a/src/program_test.cc
+++ b/src/program_test.cc
@@ -43,6 +43,15 @@
   EXPECT_TRUE(program.IsValid());
 }
 
+TEST_F(ProgramTest, IDsAreUnique) {
+  Program program_a(ProgramBuilder{});
+  Program program_b(ProgramBuilder{});
+  Program program_c(ProgramBuilder{});
+  EXPECT_NE(program_a.ID(), program_b.ID());
+  EXPECT_NE(program_b.ID(), program_c.ID());
+  EXPECT_NE(program_c.ID(), program_a.ID());
+}
+
 TEST_F(ProgramTest, Assert_GlobalVariable) {
   Global("var", ty.f32(), ast::StorageClass::kInput);
 
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index 7454054..3fab0d8 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -367,7 +367,7 @@
 class StatementBuilder : public Castable<StatementBuilder, ast::Statement> {
  public:
   /// Constructor
-  StatementBuilder() : Base(Source{}) {}
+  StatementBuilder() : Base(ProgramID(), Source{}) {}
 
   /// @param builder the program builder
   /// @returns the build AST node
diff --git a/src/reader/wgsl/parser_impl_for_stmt_test.cc b/src/reader/wgsl/parser_impl_for_stmt_test.cc
index 3e16a26..e82521d 100644
--- a/src/reader/wgsl/parser_impl_for_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_for_stmt_test.cc
@@ -32,8 +32,8 @@
     EXPECT_FALSE(e_for.errored);
     EXPECT_FALSE(p_for->has_error()) << p_for->error();
 
-    std::string loop = ast::BlockStatement({}, e_loop.value).str(Sem());
-    std::string for_ = ast::BlockStatement({}, e_for.value).str(Sem());
+    std::string loop = ast::BlockStatement({}, {}, e_loop.value).str(Sem());
+    std::string for_ = ast::BlockStatement({}, {}, e_for.value).str(Sem());
     EXPECT_EQ(loop, for_);
   }
 };
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index 2ed500e..bc27e2a 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -48,7 +48,8 @@
 
 class FakeStmt : public ast::Statement {
  public:
-  explicit FakeStmt(Source source) : ast::Statement(source) {}
+  FakeStmt(ProgramID program_id, Source source)
+      : ast::Statement(program_id, source) {}
   FakeStmt* Clone(CloneContext*) const override { return nullptr; }
   void to_str(const semantic::Info&, std::ostream& out, size_t) const override {
     out << "Fake";
@@ -57,7 +58,8 @@
 
 class FakeExpr : public ast::Expression {
  public:
-  explicit FakeExpr(Source source) : ast::Expression(source) {}
+  FakeExpr(ProgramID program_id, Source source)
+      : ast::Expression(program_id, source) {}
   FakeExpr* Clone(CloneContext*) const override { return nullptr; }
   void to_str(const semantic::Info&, std::ostream&, size_t) const override {}
 };
@@ -179,8 +181,8 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_Error_Unknown) {
-  FakeExpr e(Source{Source::Location{2, 30}});
-  WrapInFunction(&e);
+  auto* e = create<FakeExpr>(Source{Source::Location{2, 30}});
+  WrapInFunction(e);
 
   EXPECT_FALSE(r()->Resolve());
 
diff --git a/src/transform/calculate_array_length.cc b/src/transform/calculate_array_length.cc
index 3d57d9c..7d92631 100644
--- a/src/transform/calculate_array_length.cc
+++ b/src/transform/calculate_array_length.cc
@@ -51,7 +51,9 @@
 
 }  // namespace
 
-CalculateArrayLength::BufferSizeIntrinsic::BufferSizeIntrinsic() = default;
+CalculateArrayLength::BufferSizeIntrinsic::BufferSizeIntrinsic(
+    ProgramID program_id)
+    : Base(program_id) {}
 CalculateArrayLength::BufferSizeIntrinsic::~BufferSizeIntrinsic() = default;
 std::string CalculateArrayLength::BufferSizeIntrinsic::Name() const {
   return "intrinsic_buffer_size";
@@ -59,8 +61,8 @@
 
 CalculateArrayLength::BufferSizeIntrinsic*
 CalculateArrayLength::BufferSizeIntrinsic::Clone(CloneContext* ctx) const {
-  return ctx->dst->ASTNodes()
-      .Create<CalculateArrayLength::BufferSizeIntrinsic>();
+  return ctx->dst->ASTNodes().Create<CalculateArrayLength::BufferSizeIntrinsic>(
+      ctx->dst->ID());
 }
 
 CalculateArrayLength::CalculateArrayLength() = default;
@@ -93,7 +95,7 @@
           },
           ctx.dst->ty.void_(), nullptr,
           ast::DecorationList{
-              ctx.dst->ASTNodes().Create<BufferSizeIntrinsic>(),
+              ctx.dst->ASTNodes().Create<BufferSizeIntrinsic>(ctx.dst->ID()),
           },
           ast::DecorationList{});
       ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), buffer_type, func);
diff --git a/src/transform/calculate_array_length.h b/src/transform/calculate_array_length.h
index fa96d81..b4d8b0b 100644
--- a/src/transform/calculate_array_length.h
+++ b/src/transform/calculate_array_length.h
@@ -37,7 +37,8 @@
       : public Castable<BufferSizeIntrinsic, ast::InternalDecoration> {
    public:
     /// Constructor
-    BufferSizeIntrinsic();
+    /// @param program_id the identifier of the program that owns this node
+    explicit BufferSizeIntrinsic(ProgramID program_id);
     /// Destructor
     ~BufferSizeIntrinsic() override;
 
diff --git a/src/transform/decompose_storage_access.cc b/src/transform/decompose_storage_access.cc
index 54d745a..92ebe5f 100644
--- a/src/transform/decompose_storage_access.cc
+++ b/src/transform/decompose_storage_access.cc
@@ -203,48 +203,52 @@
                                                     type::Type* ty) {
   using Intrinsic = DecomposeStorageAccess::Intrinsic;
 
+  auto intrinsic = [builder](Intrinsic::Type type) {
+    return builder->ASTNodes().Create<Intrinsic>(builder->ID(), type);
+  };
+
   if (ty->Is<type::I32>()) {
-    return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadI32);
+    return intrinsic(Intrinsic::kLoadI32);
   }
   if (ty->Is<type::U32>()) {
-    return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadU32);
+    return intrinsic(Intrinsic::kLoadU32);
   }
   if (ty->Is<type::F32>()) {
-    return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadF32);
+    return intrinsic(Intrinsic::kLoadF32);
   }
   if (auto* vec = ty->As<type::Vector>()) {
     switch (vec->size()) {
       case 2:
         if (vec->type()->Is<type::I32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadVec2I32);
+          return intrinsic(Intrinsic::kLoadVec2I32);
         }
         if (vec->type()->Is<type::U32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadVec2U32);
+          return intrinsic(Intrinsic::kLoadVec2U32);
         }
         if (vec->type()->Is<type::F32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadVec2F32);
+          return intrinsic(Intrinsic::kLoadVec2F32);
         }
         break;
       case 3:
         if (vec->type()->Is<type::I32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadVec3I32);
+          return intrinsic(Intrinsic::kLoadVec3I32);
         }
         if (vec->type()->Is<type::U32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadVec3U32);
+          return intrinsic(Intrinsic::kLoadVec3U32);
         }
         if (vec->type()->Is<type::F32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadVec3F32);
+          return intrinsic(Intrinsic::kLoadVec3F32);
         }
         break;
       case 4:
         if (vec->type()->Is<type::I32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadVec4I32);
+          return intrinsic(Intrinsic::kLoadVec4I32);
         }
         if (vec->type()->Is<type::U32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadVec4U32);
+          return intrinsic(Intrinsic::kLoadVec4U32);
         }
         if (vec->type()->Is<type::F32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kLoadVec4F32);
+          return intrinsic(Intrinsic::kLoadVec4F32);
         }
         break;
     }
@@ -258,57 +262,52 @@
                                                      type::Type* ty) {
   using Intrinsic = DecomposeStorageAccess::Intrinsic;
 
+  auto intrinsic = [builder](Intrinsic::Type type) {
+    return builder->ASTNodes().Create<Intrinsic>(builder->ID(), type);
+  };
+
   if (ty->Is<type::I32>()) {
-    return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kStoreI32);
+    return intrinsic(Intrinsic::kStoreI32);
   }
   if (ty->Is<type::U32>()) {
-    return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kStoreU32);
+    return intrinsic(Intrinsic::kStoreU32);
   }
   if (ty->Is<type::F32>()) {
-    return builder->ASTNodes().Create<Intrinsic>(Intrinsic::kStoreF32);
+    return intrinsic(Intrinsic::kStoreF32);
   }
   if (auto* vec = ty->As<type::Vector>()) {
     switch (vec->size()) {
       case 2:
         if (vec->type()->Is<type::I32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(
-              Intrinsic::kStoreVec2U32);
+          return intrinsic(Intrinsic::kStoreVec2U32);
         }
         if (vec->type()->Is<type::U32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(
-              Intrinsic::kStoreVec2F32);
+          return intrinsic(Intrinsic::kStoreVec2F32);
         }
         if (vec->type()->Is<type::F32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(
-              Intrinsic::kStoreVec2I32);
+          return intrinsic(Intrinsic::kStoreVec2I32);
         }
         break;
       case 3:
         if (vec->type()->Is<type::I32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(
-              Intrinsic::kStoreVec3U32);
+          return intrinsic(Intrinsic::kStoreVec3U32);
         }
         if (vec->type()->Is<type::U32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(
-              Intrinsic::kStoreVec3F32);
+          return intrinsic(Intrinsic::kStoreVec3F32);
         }
         if (vec->type()->Is<type::F32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(
-              Intrinsic::kStoreVec3I32);
+          return intrinsic(Intrinsic::kStoreVec3I32);
         }
         break;
       case 4:
         if (vec->type()->Is<type::I32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(
-              Intrinsic::kStoreVec4U32);
+          return intrinsic(Intrinsic::kStoreVec4U32);
         }
         if (vec->type()->Is<type::U32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(
-              Intrinsic::kStoreVec4F32);
+          return intrinsic(Intrinsic::kStoreVec4F32);
         }
         if (vec->type()->Is<type::F32>()) {
-          return builder->ASTNodes().Create<Intrinsic>(
-              Intrinsic::kStoreVec4I32);
+          return intrinsic(Intrinsic::kStoreVec4I32);
         }
         break;
     }
@@ -544,7 +543,8 @@
 
 }  // namespace
 
-DecomposeStorageAccess::Intrinsic::Intrinsic(Type ty) : type(ty) {}
+DecomposeStorageAccess::Intrinsic::Intrinsic(ProgramID program_id, Type ty)
+    : Base(program_id), type(ty) {}
 DecomposeStorageAccess::Intrinsic::~Intrinsic() = default;
 std::string DecomposeStorageAccess::Intrinsic::Name() const {
   switch (type) {
@@ -602,7 +602,8 @@
 
 DecomposeStorageAccess::Intrinsic* DecomposeStorageAccess::Intrinsic::Clone(
     CloneContext* ctx) const {
-  return ctx->dst->ASTNodes().Create<DecomposeStorageAccess::Intrinsic>(type);
+  return ctx->dst->ASTNodes().Create<DecomposeStorageAccess::Intrinsic>(
+      ctx->dst->ID(), type);
 }
 
 DecomposeStorageAccess::DecomposeStorageAccess() = default;
diff --git a/src/transform/decompose_storage_access.h b/src/transform/decompose_storage_access.h
index cce09c6..3f38035 100644
--- a/src/transform/decompose_storage_access.h
+++ b/src/transform/decompose_storage_access.h
@@ -66,8 +66,9 @@
     };
 
     /// Constructor
+    /// @param program_id the identifier of the program that owns this node
     /// @param ty the type of the intrinsic
-    explicit Intrinsic(Type ty);
+    Intrinsic(ProgramID program_id, Type ty);
     /// Destructor
     ~Intrinsic() override;
 
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 81ae49a..23172b9 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -2053,7 +2053,7 @@
   auto* last_statement = func->get_last_statement();
   if (last_statement == nullptr ||
       !last_statement->Is<ast::ReturnStatement>()) {
-    ast::ReturnStatement ret(Source{});
+    ast::ReturnStatement ret(ProgramID(), Source{});
     if (!EmitStatement(out, &ret)) {
       return false;
     }
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 5788eb9..ddee33e 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -1546,7 +1546,7 @@
   auto* last_statement = func->get_last_statement();
   if (last_statement == nullptr ||
       !last_statement->Is<ast::ReturnStatement>()) {
-    ast::ReturnStatement ret(Source{});
+    ast::ReturnStatement ret(ProgramID{}, Source{});
     if (!EmitStatement(&ret)) {
       return false;
     }
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 005c606..69a0361 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -752,16 +752,16 @@
     //    then WGSL requires an initializer.
     if (ast::HasDecoration<ast::ConstantIdDecoration>(var->decorations())) {
       if (type_no_ac->Is<type::F32>()) {
-        ast::FloatLiteral l(Source{}, type_no_ac, 0.0f);
+        ast::FloatLiteral l(ProgramID(), Source{}, type_no_ac, 0.0f);
         init_id = GenerateLiteralIfNeeded(var, &l);
       } else if (type_no_ac->Is<type::U32>()) {
-        ast::UintLiteral l(Source{}, type_no_ac, 0);
+        ast::UintLiteral l(ProgramID(), Source{}, type_no_ac, 0);
         init_id = GenerateLiteralIfNeeded(var, &l);
       } else if (type_no_ac->Is<type::I32>()) {
-        ast::SintLiteral l(Source{}, type_no_ac, 0);
+        ast::SintLiteral l(ProgramID(), Source{}, type_no_ac, 0);
         init_id = GenerateLiteralIfNeeded(var, &l);
       } else if (type_no_ac->Is<type::Bool>()) {
-        ast::BoolLiteral l(Source{}, type_no_ac, false);
+        ast::BoolLiteral l(ProgramID(), Source{}, type_no_ac, false);
         init_id = GenerateLiteralIfNeeded(var, &l);
       } else {
         error_ = "invalid type for constant_id, must be scalar";
@@ -2303,7 +2303,8 @@
         op = spv::Op::OpImageQuerySizeLod;
         spirv_params.emplace_back(gen(level));
       } else {
-        ast::SintLiteral i32_0(Source{}, builder_.create<type::I32>(), 0);
+        ast::SintLiteral i32_0(ProgramID(), Source{},
+                               builder_.create<type::I32>(), 0);
         op = spv::Op::OpImageQuerySizeLod;
         spirv_params.emplace_back(
             Operand::Int(GenerateLiteralIfNeeded(nullptr, &i32_0)));
@@ -2335,7 +2336,8 @@
           texture_type->Is<type::StorageTexture>()) {
         op = spv::Op::OpImageQuerySize;
       } else {
-        ast::SintLiteral i32_0(Source{}, builder_.create<type::I32>(), 0);
+        ast::SintLiteral i32_0(ProgramID(), Source{},
+                               builder_.create<type::I32>(), 0);
         op = spv::Op::OpImageQuerySizeLod;
         spirv_params.emplace_back(
             Operand::Int(GenerateLiteralIfNeeded(nullptr, &i32_0)));
@@ -2413,7 +2415,7 @@
         // Depth textures have i32 parameters for the level, but SPIR-V expects
         // F32. Cast.
         auto* f32 = builder_.create<type::F32>();
-        ast::TypeConstructorExpression cast(Source{}, f32,
+        ast::TypeConstructorExpression cast(ProgramID(), Source{}, f32,
                                             {arg(Usage::kLevel)});
         level = Operand::Int(GenerateExpression(&cast));
         if (level.to_i() == 0) {
@@ -2446,7 +2448,7 @@
       spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
 
       type::F32 f32;
-      ast::FloatLiteral float_0(Source{}, &f32, 0.0);
+      ast::FloatLiteral float_0(ProgramID(), Source{}, &f32, 0.0);
       image_operands.emplace_back(ImageOperand{
           SpvImageOperandsLodMask,
           Operand::Int(GenerateLiteralIfNeeded(nullptr, &float_0))});