On this page:
9.1 API for Decompiling
9.2 API for Parsing Bytecode
9.3 API for Marshaling Bytecode
9.4 Bytecode Representation
9.4.1 Prefix
9.4.2 Forms
9.4.3 Expressions
9.4.4 Syntax Objects

9 raco decompile: Decompiling Bytecode

The raco decompile command takes a bytecode file (which usually has the file extension ".zo") or a source file with an associated bytecode file (usually created with raco make) and converts it back to an approximation of Racket code. Decompiled bytecode is mostly useful for checking the compiler’s transformation and optimization of the source program.

Many forms in the decompiled code, such as module, define, and lambda, have the same meanings as always. Other forms and transformations are specific to the rendering of bytecode, and they reflect a specific execution model:

9.1 API for Decompiling

 (require compiler/decompile)

(decompile top)  any/c
  top : compilation-top?
Consumes the result of parsing bytecode and returns an S-expression (as described above) that represents the compiled code.

9.2 API for Parsing Bytecode

 (require compiler/zo-parse)

The compiler/zo-parse module re-exports compiler/zo-structs in addition to zo-parse.

Parses a port (typically the result of opening a ".zo" file) containing bytecode. Beware that the structure types used to represent the bytecode are subject to frequent changes across Racket versons.

The parsed bytecode is returned in a compilation-top structure. For a compiled module, the compilation-top structure will contain a mod structure. For a top-level sequence, it will normally contain a seq or splice structure with a list of top-level declarations and expressions.

The bytecode representation of an expression is closer to an S-expression than a traditional, flat control string. For example, an if form is represented by a branch structure that has three fields: a test expression, a “then” expression, and an “else” expression. Similarly, a function call is represented by an application structure that has a list of argument expressions.

Storage for local variables or intermediate values (such as the arguments for a function call) is explicitly specified in terms of a stack. For example, execution of an application structure reserves space on the stack for each argument result. Similarly, when a let-one structure (for a simple let) is executed, the value obtained by evaluating the right-hand side expression is pushed onto the stack, and then the body is evaluated. Local variables are always accessed as offsets from the current stack position. When a function is called, its arguments are passed on the stack. A closure is created by transferring values from the stack to a flat closure record, and when a closure is applied, the saved values are restored on the stack (though possibly in a different order and likely in a more compact layout than when they were captured).

When a sub-expression produces a value, then the stack pointer is restored to its location from before evaluating the sub-expression. For example, evaluating the right-hand size for a let-one structure may temporarily push values onto the stack, but the stack is restored to its pre-let-one position before pushing the resulting value and continuing with the body. In addition, a tail call resets the stack pointer to the position that follows the enclosing function’s arguments, and then the tail call continues by pushing onto the stack the arguments for the tail-called function.

Values for global and module-level variables are not put directly on the stack, but instead stored in “buckets,” and an array of accessible buckets is kept on the stack. When a closure body needs to access a global variable, the closure captures and later restores the bucket array in the same way that it captured and restores a local variable. Mutable local variables are boxed similarly to global variables, but individual boxes are referenced from the stack and closures.

Quoted syntax (in the sense of quote-syntax) is treated like a global variable, because it must be instantiated for an appropriate phase. A prefix structure within a compilation-top or mod structure indicates the list of global variables and quoted syntax that need to be instantiated (and put into an array on the stack) before evaluating expressions that might use them.

9.3 API for Marshaling Bytecode

 (require compiler/zo-marshal)

(zo-marshal-to top out)  void?
  top : compilation-top?
  out : output-port?
Consumes a representation of bytecode and writes it to out.

(zo-marshal top)  bytes?
  top : compilation-top?
Consumes a representation of bytecode and generates a byte string for the marshaled bytecode.

9.4 Bytecode Representation

 (require compiler/zo-structs)

The compiler/zo-structs library defines the bytecode structures that are produced by zo-parse and consumed by decompile and zo-marshal.

(struct zo ()
  #:extra-constructor-name make-zo
A supertype for all forms that can appear in compiled code.

9.4.1 Prefix

(struct compilation-top zo (max-let-depth prefix code)
  #:extra-constructor-name make-compilation-top
  max-let-depth : exact-nonnegative-integer?
  prefix : prefix?
  code : (or/c form? any/c)
Wraps compiled code. The max-let-depth field indicates the maximum stack depth that code creates (not counting the prefix array). The prefix field describes top-level variables, module-level variables, and quoted syntax-objects accessed by code. The code field contains executable code; it is normally a form, but a literal value is represented as itself.

(struct prefix zo (num-lifts toplevels stxs)
  #:extra-constructor-name make-prefix
  num-lifts : exact-nonnegative-integer?
  toplevels : 
(listof (or/c #f symbol? global-bucket?
  stxs : (listof stx?)
Represents a “prefix” that is pushed onto the stack to initiate evaluation. The prefix is an array, where buckets holding the values for toplevels are first, then the buckets for the stxs, then a bucket for another array if stxs is non-empty, then num-lifts extra buckets for lifted local procedures.

In toplevels, each element is one of the following:
  • a #f, which indicates a dummy variable that is used to access the enclosing module/namespace at run time;

  • a symbol, which is a reference to a variable defined in the enclosing module;

  • a global-bucket, which is a top-level variable (appears only outside of modules); or

  • a module-variable, which indicates a variable imported from another module.

The variable buckets and syntax objects that are recorded in a prefix are accessed by toplevel and topsyntax expression forms.

(struct global-bucket zo (name)
  #:extra-constructor-name make-global-bucket
  name : symbol?
Represents a top-level variable, and used only in a prefix.

(struct module-variable zo (modidx sym pos phase)
  #:extra-constructor-name make-module-variable
  modidx : module-path-index?
  sym : symbol?
  pos : exact-integer?
  phase : (or/c 0 1)
Represents a top-level variable, and used only in a prefix. The pos may record the variable’s offset within its module, or it can be -1 if the variable is always located by name. The phase indicates the phase level of the definition within its module.

(struct stx zo (encoded)
  #:extra-constructor-name make-stx
  encoded : wrapped?
Wraps a syntax object in a prefix.

9.4.2 Forms

(struct form zo ()
  #:extra-constructor-name make-form
A supertype for all forms that can appear in compiled code (including exprs), except for literals that are represented as themselves.

(struct def-values form (ids rhs)
  #:extra-constructor-name make-def-values
  ids : (listof toplevel?)
  rhs : (or/c expr? seq? inline-variant? any/c)
Represents a define-values form. Each element of ids will reference via the prefix either a top-level variable or a local module variable.

After rhs is evaluated, the stack is restored to its depth from before evaluating rhs.

(struct def-syntaxes form (ids rhs prefix max-let-depth dummy)
  #:extra-constructor-name make-def-syntaxes
  ids : (listof symbol?)
  rhs : (or/c expr? seq? any/c)
  prefix : prefix?
  max-let-depth : exact-nonnegative-integer?
  dummy : (or/c toplevel? #f)
(struct seq-for-syntax form (forms prefix max-let-depth dummy)
  #:extra-constructor-name make-seq-for-syntax
  forms : (listof (or/c form? any/c))
  prefix : prefix?
  max-let-depth : exact-nonnegative-integer?
  dummy : (or/c toplevel? #f)
Represents a define-syntaxes or begin-for-syntax form. The rhs expression or set of forms forms has its own prefix, which is pushed before evaluating rhs or the forms; the stack is restored after obtaining the result values. The max-let-depth field indicates the maximum size of the stack that will be created by rhs (not counting prefix). The dummy variable is used to access the enclosing namespace.

(struct req form (reqs dummy)
  #:extra-constructor-name make-req
  reqs : stx?
  dummy : toplevel?
Represents a top-level #%require form (but not one in a module form) with a sequence of specifications reqs. The dummy variable is used to access the top-level namespace.

(struct seq form (forms)
  #:extra-constructor-name make-seq
  forms : (listof (or/c form? any/c))
Represents a begin form, either as an expression or at the top level (though the latter is more commonly a splice form). When a seq appears in an expression position, its forms are expressions.

After each form in forms is evaluated, the stack is restored to its depth from before evaluating the form.

(struct splice form (forms)
  #:extra-constructor-name make-splice
  forms : (listof (or/c form? any/c))
Represents a top-level begin form where each evaluation is wrapped with a continuation prompt.

After each form in forms is evaluated, the stack is restored to its depth from before evaluating the form.

(struct inline-variant form (direct inline)
  #:extra-constructor-name make-inline-variant
  direct : expr?
  inline : expr?
Represents a function that is bound by define-values, where the function has two variants. The first variant is used for normal calls to the function. The second may be used for cross-module inlining of the function.

(struct mod form (name
  #:extra-constructor-name make-mod
  name : symbol?
  srcname : symbol?
  self-modidx : module-path-index?
  prefix : prefix?
  provides : 
(listof (list/c (or/c exact-integer? #f)
                (listof provided?)
                (listof provided?)))
  requires : 
(listof (cons/c (or/c exact-integer? #f)
                (listof module-path-index?)))
  body : (listof (or/c form? any/c))
  syntax-bodies : 
(listof (cons/c exact-positive-integer?
                (listof (or/c def-syntaxes?
  unexported : 
(listof (list/c exact-nonnegative-integer?
                (listof symbol?)
                (listof symbol?)))
  max-let-depth : exact-nonnegative-integer?
  dummy : toplevel?
  lang-info : (or/c #f (vector/c module-path? symbol? any/c))
  internal-context : (or/c #f #t stx?)
Represents a module declaration.

The provides and requires lists are each an association list from phases to exports or imports. In the case of provides, each phase maps to two lists: one for exported variables, and another for exported syntax. In the case of requires, each phase maps to a list of imported module paths.

The body field contains the module’s run-time (i.e., phase 0) code. The syntax-bodies list has a list of forms for each higher phase in the module body; the phases are in order starting with phase 1. The body forms use prefix, rather than any prefix in place for the module declaration itself, while members of lists in syntax-bodies have their own prefixes. After each form in body or syntax-bodies is evaluated, the stack is restored to its depth from before evaluating the form.

The unexported list contains lists of symbols for unexported definitions that can be accessed through macro expansion and that are implemented through the forms in body and syntax-bodies. Each list in unexported starts with a phase level.

The max-let-depth field indicates the maximum stack depth created by body forms (not counting the prefix array). The dummy variable is used to access to the top-level namespace.

The lang-info value specifies an optional module path that provides information about the module’s implementation language.

The internal-module-context value describes the lexical context of the body of the module. This value is used by module->namespace. A #f value means that the context is unavailable or empty. A #t value means that the context is computed by re-importing all required modules. A syntax-object value embeds an arbitrary lexical context.

(struct provided (name src src-name nom-src src-phase protected?)
  #:extra-constructor-name make-provided
  name : symbol?
  src : (or/c module-path-index? #f)
  src-name : symbol?
  nom-src : (or/c module-path-index? #f)
  src-phase : exact-nonnegative-integer?
  protected? : boolean?
Describes an individual provided identifier within a mod instance.

9.4.3 Expressions

(struct expr form ()
  #:extra-constructor-name make-expr
A supertype for all expression forms that can appear in compiled code, except for literals that are represented as themselves and some seq structures (which can appear as an expression as long as it contains only other things that can be expressions).

(struct lam expr (name
  #:extra-constructor-name make-lam
  name : (or/c symbol? vector?)
  flags : 
(listof (or/c 'preserves-marks 'is-method 'single-result
              'only-rest-arg-not-used 'sfs-clear-rest-args))
  num-params : exact-nonnegative-integer?
  param-types : (listof (or/c 'val 'ref 'flonum))
  rest? : boolean?
  closure-map : (vectorof exact-nonnegative-integer?)
  closure-types : (listof (or/c 'val/ref 'flonum))
  toplevel-map : (or/c #f (set/c exact-nonnegative-integer?))
  max-let-depth : exact-nonnegative-integer?
  body : (or/c expr? seq? any/c)
Represents a lambda form. The name field is a name for debugging purposes. The num-params field indicates the number of arguments accepted by the procedure, not counting a rest argument; the rest? field indicates whether extra arguments are accepted and collected into a “rest” variable. The param-types list contains num-params symbols indicating the type of each argumet, either 'val for a normal argument, 'ref for a boxed argument (representing a mutable local variable), or 'flonum for a flonum argument.

The closure-map field is a vector of stack positions that are captured when evaluating the lambda form to create a closure. The closure-types field provides a corresponding list of types, but no distinction is made between normal values and boxed values; also, this information is redundant, since it can be inferred by the bindings referenced though closure-map.

Which a closure captures top-level or module-level variables, they are represented in the closure by capturing a prefix (in the sense of prefix). The toplevel-map field indicates which top-level and lifted variables are actually used by the closure (so that variables in a prefix can be pruned by the run-time system if they become unused). A #f value indicates either that no prefix is captured or all variables in the prefix should be considered used. Otherwise, numbers in the set indicate which variables and lifted variables are used. Variables are numbered consecutively by position in the prefix starting from 0. Lifted variables are numbered immediately afterward—which means that, if the prefix contains any syntax objects, lifted-variable numbers are shifted down relative to a toplevel by the number of syntax object in the prefix plus one (which makes the toplevel-map set more compact).

When the function is called, the rest-argument list (if any) is pushed onto the stack, then the normal arguments in reverse order, then the closure-captured values in reverse order. Thus, when body is run, the first value on the stack is the first value captured by the closure-map array, and so on.

The max-let-depth field indicates the maximum stack depth created by body plus the arguments and closure-captured values pushed onto the stack. The body field is the expression for the closure’s body.

(struct closure expr (code gen-id)
  #:extra-constructor-name make-closure
  code : lam?
  gen-id : symbol?
A lambda form with an empty closure, which is a procedure constant. The procedure constant can appear multiple times in the graph of expressions for bytecode, and the code field can be a cycle for a recursive constant procedure; the gen-id is different for each such constant.

(struct case-lam expr (name clauses)
  #:extra-constructor-name make-case-lam
  name : (or/c symbol? vector?)
  clauses : (listof lam?)
Represents a case-lambda form as a combination of lambda forms that are tried (in order) based on the number of arguments given.

(struct let-one expr (rhs body flonum? unused?)
  #:extra-constructor-name make-let-one
  rhs : (or/c expr? seq? any/c)
  body : (or/c expr? seq? any/c)
  flonum? : boolean?
  unused? : boolean?
Pushes an uninitialized slot onto the stack, evaluates rhs and puts its value into the slot, and then runs body. If flonum? is #t, then rhs must produce a flonum, and the slot must be accessed by localrefs that expect a flonum. If unused? is #t, then the slot must not be used, and the value of rhs is not actually pushed onto the stack (but rhs is constrained to produce a single value).

After rhs is evaluated, the stack is restored to its depth from before evaluating rhs. Note that the new slot is created before evaluating rhs.

(struct let-void expr (count boxes? body)
  #:extra-constructor-name make-let-void
  count : exact-nonnegative-integer?
  boxes? : boolean?
  body : (or/c expr? seq? any/c)
Pushes count uninitialized slots onto the stack and then runs body. If boxes? is #t, then the slots are filled with boxes that contain #<undefined>.

(struct install-value expr (count pos boxes? rhs body)
  #:extra-constructor-name make-install-value
  count : exact-nonnegative-integer?
  pos : exact-nonnegative-integer?
  boxes? : boolean?
  rhs : (or/c expr? seq? any/c)
  body : (or/c expr? seq? any/c)
Runs rhs to obtain count results, and installs them into existing slots on the stack in order, skipping the first pos stack positions. If boxes? is #t, then the values are put into existing boxes in the stack slots.

After rhs is evaluated, the stack is restored to its depth from before evaluating rhs.

(struct let-rec expr (procs body)
  #:extra-constructor-name make-let-rec
  procs : (listof lam?)
  body : (or/c expr? seq? any/c)
Represents a letrec form with lambda bindings. It allocates a closure shell for each lambda form in procs, installs each onto the stack in previously allocated slots in reverse order (so that the closure shell for the last element of procs is installed at stack position 0), fills out each shell’s closure (where each closure normally references some other just-created closures, which is possible because the shells have been installed on the stack), and then evaluates body.

(struct boxenv expr (pos body)
  #:extra-constructor-name make-boxenv
  pos : exact-nonnegative-integer?
  body : (or/c expr? seq? any/c)
Skips pos elements of the stack, setting the slot afterward to a new box containing the slot’s old value, and then runs body. This form appears when a lambda argument is mutated using set! within its body; calling the function initially pushes the value directly on the stack, and this form boxes the value so that it can be mutated later.

(struct localref expr (unbox? pos clear? other-clears? flonum?)
  #:extra-constructor-name make-localref
  unbox? : boolean?
  pos : exact-nonnegative-integer?
  clear? : boolean?
  other-clears? : boolean?
  flonum? : boolean?
Represents a local-variable reference; it accesses the value in the stack slot after the first pos slots. If unbox? is #t, the stack slot contains a box, and a value is extracted from the box. If clear? is #t, then after the value is obtained, the stack slot is cleared (to avoid retaining a reference that can prevent reclamation of the value as garbage). If other-clears? is #t, then some later reference to the same stack slot may clear after reading. If flonum? is #t, the slot holds to a flonum value.

(struct toplevel expr (depth pos const? ready?)
  #:extra-constructor-name make-toplevel
  depth : exact-nonnegative-integer?
  pos : exact-nonnegative-integer?
  const? : boolean?
  ready? : boolean?
Represents a reference to a top-level or imported variable via the prefix array. The depth field indicates the number of stack slots to skip to reach the prefix array, and pos is the offset into the array.

When the toplevel is an expression, if both const? and ready? are #t, then the variable definitely will be defined, its value stays constant, and the constant is effectively the same for every module instantiation. If only const? is #t, then the value is constant, but it may vary across instantiations. If only ready? is #t, then the variable definitely will be defined, but its value may change. If const? and ready? are both #f, then a check is needed to determine whether the variable is defined.

When the toplevel is the right-hand side for def-values, then const? is #f. If ready? is #t, the variable is marked as immutable after it is defined.

(struct topsyntax expr (depth pos midpt)
  #:extra-constructor-name make-topsyntax
  depth : exact-nonnegative-integer?
  pos : exact-nonnegative-integer?
  midpt : exact-nonnegative-integer?
Represents a reference to a quoted syntax object via the prefix array. The depth field indicates the number of stack slots to skip to reach the prefix array, and pos is the offset into the array. The midpt value is used internally for lazy calculation of syntax information.

(struct application expr (rator rands)
  #:extra-constructor-name make-application
  rator : (or/c expr? seq? any/c)
  rands : (listof (or/c expr? seq? any/c))
Represents a function call. The rator field is the expression for the function, and rands are the argument expressions. Before any of the expressions are evaluated, (length rands) uninitialized stack slots are created (to be used as temporary space).

(struct branch expr (test then else)
  #:extra-constructor-name make-branch
  test : (or/c expr? seq? any/c)
  then : (or/c expr? seq? any/c)
  else : (or/c expr? seq? any/c)
Represents an if form.

After test is evaluated, the stack is restored to its depth from before evaluating test.

(struct with-cont-mark expr (key val body)
  #:extra-constructor-name make-with-cont-mark
  key : (or/c expr? seq? any/c)
  val : (or/c expr? seq? any/c)
  body : (or/c expr? seq? any/c)
Represents a with-continuation-mark expression.

After each of key and val is evaluated, the stack is restored to its depth from before evaluating key or val.

(struct beg0 expr (seq)
  #:extra-constructor-name make-beg0
  seq : (listof (or/c expr? seq? any/c))
Represents a begin0 expression.

After each expression in seq is evaluated, the stack is restored to its depth from before evaluating the expression.

(struct varref expr (toplevel dummy)
  #:extra-constructor-name make-varref
  toplevel : (or/c toplevel? #t)
  dummy : (or/c toplevel? #f)
Represents a #%variable-reference form. The toplevel field is #t if the original reference was to a constant local binding. The dummy field accesses a variable bucket that strongly references its namespace (as opposed to a normal variable bucket, which only weakly references its namespace); it can be #f.

(struct assign expr (id rhs undef-ok?)
  #:extra-constructor-name make-assign
  id : toplevel?
  rhs : (or/c expr? seq? any/c)
  undef-ok? : boolean?
Represents a set! expression that assigns to a top-level or module-level variable. (Assignments to local variables are represented by install-value expressions.)

After rhs is evaluated, the stack is restored to its depth from before evaluating rhs.

(struct apply-values expr (proc args-expr)
  #:extra-constructor-name make-apply-values
  proc : (or/c expr? seq? any/c)
  args-expr : (or/c expr? seq? any/c)
Represents (call-with-values (lambda () args-expr) proc), which is handled specially by the run-time system.

(struct primval expr (id)
  #:extra-constructor-name make-primval
  id : exact-nonnegative-integer?
Represents a direct reference to a variable imported from the run-time kernel.

9.4.4 Syntax Objects

(struct wrapped zo (datum wraps tamper-status)
  #:extra-constructor-name make-wrapped
  datum : any/c
  wraps : (listof wrap?)
  tamper-status : (or/c 'clean 'armed 'tainted)
Represents a syntax object, where wraps contain the lexical information and tamper-status is taint information. When the datum part is itself compound, its pieces are wrapped, too.

(struct wrap zo ()
  #:extra-constructor-name make-wrap
A supertype for lexical-information elements.

(struct top-level-rename wrap (flag)
  #:extra-constructor-name make-top-level-rename
  flag : boolean?
A top-level renaming.

(struct mark-barrier wrap (value)
  #:extra-constructor-name make-mark-barrier
  value : symbol?
A mark barrier.

(struct free-id-info zo (path0
  #:extra-constructor-name make-free-id-info
  path0 : module-path-index?
  symbol0 : symbol?
  path1 : module-path-index?
  symbol1 : symbol?
  phase0 : (or/c exact-integer? #f)
  phase1 : (or/c exact-integer? #f)
  phase2 : (or/c exact-integer? #f)
  use-current-inspector? : boolean?
Information about a free identifier.

(struct lexical-rename wrap (has-free-id-info? bool2 alist)
  #:extra-constructor-name make-lexical-rename
  has-free-id-info? : boolean?
  bool2 : boolean?
  alist : 
 (cons/c symbol?
         (or/c symbol?
               (cons/c symbol?
                       (or/c (cons/c symbol? (or/c symbol? #f))
A local-binding mapping from symbols to binding-set names.

(struct phase-shift wrap (amt src dest)
  #:extra-constructor-name make-phase-shift
  amt : exact-integer?
  src : module-path-index?
  dest : module-path-index?
Shifts module bindings later in the wrap set.

(struct module-rename wrap (phase
  #:extra-constructor-name make-module-rename
  phase : exact-integer?
  kind : (or/c 'marked 'normal)
  set-id : any/c
  unmarshals : (listof make-all-from-module?)
  renames : (listof module-binding?)
  mark-renames : any/c
  plus-kern? : boolean?
Represents a set of module and import bindings.

(struct all-from-module zo (path phase src-phase exceptions prefix)
  #:extra-constructor-name make-all-from-module
  path : module-path-index?
  phase : (or/c exact-integer? #f)
  src-phase : (or/c exact-integer? #f)
  exceptions : (listof (or/c symbol? number?))
  prefix : (or/c symbol? #f)
Represents a set of simple imports from one module within a module-rename.

(struct module-binding zo ()
  #:extra-constructor-name make-module-binding
A supertype for module bindings.

(struct simple-module-binding module-binding (path)
  #:extra-constructor-name make-simple-module-binding
  path : module-path-index?
Represents a single identifier import within a module-rename.

(struct phased-module-binding module-binding (path
  #:extra-constructor-name make-phased-module-binding
  path : module-path-index?
  phase : exact-integer?
  export-name : any/c
  nominal-path : nominal-path?
  nominal-export-name : any/c
Represents a single identifier import within a module-rename.

(struct exported-nominal-module-binding module-binding (path
  path : module-path-index?
  export-name : any/c
  nominal-path : nominal-path?
  nominal-export-name : any/c
Represents a single identifier import within a module-rename.

(struct nominal-module-binding module-binding (path nominal-path)
  #:extra-constructor-name make-nominal-module-binding
  path : module-path-index?
  nominal-path : nominal-path?
Represents a single identifier import within a module-rename.

(struct exported-module-binding module-binding (path export-name)
  #:extra-constructor-name make-exported-module-binding
  path : module-path-index?
  export-name : any/c
Represents a single identifier import within a module-rename.

(struct nominal-path zo ()
  #:extra-constructor-name make-nominal-path
A supertype for nominal paths.

(struct simple-nominal-path nominal-path (value)
  #:extra-constructor-name make-simple-nominal-path
  value : module-path-index?
Represents a simple nominal path.

(struct imported-nominal-path nominal-path (value import-phase)
  #:extra-constructor-name make-imported-nominal-path
  value : module-path-index?
  import-phase : exact-integer?
Represents an imported nominal path.

(struct phased-nominal-path nominal-path (value import-phase phase)
  #:extra-constructor-name make-phased-nominal-path
  value : module-path-index?
  import-phase : (or/c false/c exact-integer?)
  phase : exact-integer?
Represents a phased nominal path.