|  | 
|  | 1 | +/** | 
|  | 2 | + * Copyright (c) Meta Platforms, Inc. and affiliates. | 
|  | 3 | + * | 
|  | 4 | + * This source code is licensed under the MIT license found in the | 
|  | 5 | + * LICENSE file in the root directory of this source tree. | 
|  | 6 | + */ | 
|  | 7 | + | 
|  | 8 | +import { | 
|  | 9 | +  ArrayExpression, | 
|  | 10 | +  BasicBlock, | 
|  | 11 | +  CallExpression, | 
|  | 12 | +  Destructure, | 
|  | 13 | +  Effect, | 
|  | 14 | +  Environment, | 
|  | 15 | +  GeneratedSource, | 
|  | 16 | +  HIRFunction, | 
|  | 17 | +  IdentifierId, | 
|  | 18 | +  Instruction, | 
|  | 19 | +  LoadLocal, | 
|  | 20 | +  Place, | 
|  | 21 | +  PropertyLoad, | 
|  | 22 | +  isUseContextHookType, | 
|  | 23 | +  makeBlockId, | 
|  | 24 | +  makeIdentifierId, | 
|  | 25 | +  makeIdentifierName, | 
|  | 26 | +  makeInstructionId, | 
|  | 27 | +  makeTemporary, | 
|  | 28 | +  makeType, | 
|  | 29 | +  markInstructionIds, | 
|  | 30 | +  mergeConsecutiveBlocks, | 
|  | 31 | +  removeUnnecessaryTryCatch, | 
|  | 32 | +  reversePostorderBlocks, | 
|  | 33 | +} from '../HIR'; | 
|  | 34 | +import { | 
|  | 35 | +  removeDeadDoWhileStatements, | 
|  | 36 | +  removeUnreachableForUpdates, | 
|  | 37 | +} from '../HIR/HIRBuilder'; | 
|  | 38 | +import {enterSSA} from '../SSA'; | 
|  | 39 | +import {inferTypes} from '../TypeInference'; | 
|  | 40 | + | 
|  | 41 | +export function lowerContextAccess(fn: HIRFunction): void { | 
|  | 42 | +  const contextAccess: Map<IdentifierId, CallExpression> = new Map(); | 
|  | 43 | +  const contextKeys: Map<IdentifierId, Array<string>> = new Map(); | 
|  | 44 | + | 
|  | 45 | +  // collect context access and keys | 
|  | 46 | +  for (const [, block] of fn.body.blocks) { | 
|  | 47 | +    for (const instr of block.instructions) { | 
|  | 48 | +      const {value, lvalue} = instr; | 
|  | 49 | + | 
|  | 50 | +      if ( | 
|  | 51 | +        value.kind === 'CallExpression' && | 
|  | 52 | +        isUseContextHookType(value.callee.identifier) | 
|  | 53 | +      ) { | 
|  | 54 | +        contextAccess.set(lvalue.identifier.id, value); | 
|  | 55 | +        continue; | 
|  | 56 | +      } | 
|  | 57 | + | 
|  | 58 | +      if (value.kind !== 'Destructure') { | 
|  | 59 | +        continue; | 
|  | 60 | +      } | 
|  | 61 | + | 
|  | 62 | +      const destructureId = value.value.identifier.id; | 
|  | 63 | +      if (!contextAccess.has(destructureId)) { | 
|  | 64 | +        continue; | 
|  | 65 | +      } | 
|  | 66 | + | 
|  | 67 | +      const keys = getContextKeys(value); | 
|  | 68 | +      if (keys === null) { | 
|  | 69 | +        continue; | 
|  | 70 | +      } | 
|  | 71 | + | 
|  | 72 | +      if (contextKeys.has(destructureId)) { | 
|  | 73 | +        /* | 
|  | 74 | +         * TODO(gsn): Add support for accessing context over multiple | 
|  | 75 | +         * statements. | 
|  | 76 | +         */ | 
|  | 77 | +        return; | 
|  | 78 | +      } else { | 
|  | 79 | +        contextKeys.set(destructureId, keys); | 
|  | 80 | +      } | 
|  | 81 | +    } | 
|  | 82 | +  } | 
|  | 83 | + | 
|  | 84 | +  if (contextAccess.size > 0) { | 
|  | 85 | +    for (const [, block] of fn.body.blocks) { | 
|  | 86 | +      const nextInstructions: Array<Instruction> = []; | 
|  | 87 | +      for (const instr of block.instructions) { | 
|  | 88 | +        const {lvalue, value} = instr; | 
|  | 89 | +        if ( | 
|  | 90 | +          value.kind === 'CallExpression' && | 
|  | 91 | +          isUseContextHookType(value.callee.identifier) && | 
|  | 92 | +          contextKeys.has(lvalue.identifier.id) | 
|  | 93 | +        ) { | 
|  | 94 | +          const keys = contextKeys.get(lvalue.identifier.id)!; | 
|  | 95 | +          const selectorFnInstr = emitSelectorFn(fn.env, keys); | 
|  | 96 | +          nextInstructions.push(selectorFnInstr); | 
|  | 97 | + | 
|  | 98 | +          const selectorFn = selectorFnInstr.lvalue; | 
|  | 99 | +          value.args.push(selectorFn); | 
|  | 100 | +        } | 
|  | 101 | + | 
|  | 102 | +        nextInstructions.push(instr); | 
|  | 103 | +      } | 
|  | 104 | +      block.instructions = nextInstructions; | 
|  | 105 | +    } | 
|  | 106 | +    markInstructionIds(fn.body); | 
|  | 107 | +  } | 
|  | 108 | +} | 
|  | 109 | + | 
|  | 110 | +function getContextKeys(value: Destructure): Array<string> | null { | 
|  | 111 | +  const keys = []; | 
|  | 112 | +  const pattern = value.lvalue.pattern; | 
|  | 113 | + | 
|  | 114 | +  switch (pattern.kind) { | 
|  | 115 | +    case 'ArrayPattern': { | 
|  | 116 | +      for (const place of pattern.items) { | 
|  | 117 | +        if (place.kind !== 'Identifier') { | 
|  | 118 | +          return null; | 
|  | 119 | +        } | 
|  | 120 | + | 
|  | 121 | +        if (place.identifier.name === null) { | 
|  | 122 | +          return null; | 
|  | 123 | +        } | 
|  | 124 | + | 
|  | 125 | +        keys.push(place.identifier.name.value); | 
|  | 126 | +      } | 
|  | 127 | +      return keys; | 
|  | 128 | +    } | 
|  | 129 | + | 
|  | 130 | +    case 'ObjectPattern': { | 
|  | 131 | +      for (const place of pattern.properties) { | 
|  | 132 | +        if ( | 
|  | 133 | +          place.kind !== 'ObjectProperty' || | 
|  | 134 | +          place.type !== 'property' || | 
|  | 135 | +          place.key.kind !== 'identifier' | 
|  | 136 | +        ) { | 
|  | 137 | +          return null; | 
|  | 138 | +        } | 
|  | 139 | +        keys.push(place.key.name); | 
|  | 140 | +      } | 
|  | 141 | +      return keys; | 
|  | 142 | +    } | 
|  | 143 | +  } | 
|  | 144 | +} | 
|  | 145 | + | 
|  | 146 | +function emitPropertyLoad( | 
|  | 147 | +  env: Environment, | 
|  | 148 | +  obj: Place, | 
|  | 149 | +  property: string, | 
|  | 150 | +): {instructions: Array<Instruction>; element: Place} { | 
|  | 151 | +  const loadObj: LoadLocal = { | 
|  | 152 | +    kind: 'LoadLocal', | 
|  | 153 | +    place: obj, | 
|  | 154 | +    loc: GeneratedSource, | 
|  | 155 | +  }; | 
|  | 156 | +  const object: Place = { | 
|  | 157 | +    kind: 'Identifier', | 
|  | 158 | +    identifier: makeTemporary(env.nextIdentifierId, GeneratedSource), | 
|  | 159 | +    effect: Effect.Unknown, | 
|  | 160 | +    reactive: false, | 
|  | 161 | +    loc: GeneratedSource, | 
|  | 162 | +  }; | 
|  | 163 | +  const loadLocalInstr: Instruction = { | 
|  | 164 | +    lvalue: object, | 
|  | 165 | +    value: loadObj, | 
|  | 166 | +    id: makeInstructionId(0), | 
|  | 167 | +    loc: GeneratedSource, | 
|  | 168 | +  }; | 
|  | 169 | + | 
|  | 170 | +  const loadProp: PropertyLoad = { | 
|  | 171 | +    kind: 'PropertyLoad', | 
|  | 172 | +    object, | 
|  | 173 | +    property, | 
|  | 174 | +    loc: GeneratedSource, | 
|  | 175 | +  }; | 
|  | 176 | +  const element: Place = { | 
|  | 177 | +    kind: 'Identifier', | 
|  | 178 | +    identifier: makeTemporary(env.nextIdentifierId, GeneratedSource), | 
|  | 179 | +    effect: Effect.Unknown, | 
|  | 180 | +    reactive: false, | 
|  | 181 | +    loc: GeneratedSource, | 
|  | 182 | +  }; | 
|  | 183 | +  const loadPropInstr: Instruction = { | 
|  | 184 | +    lvalue: element, | 
|  | 185 | +    value: loadProp, | 
|  | 186 | +    id: makeInstructionId(0), | 
|  | 187 | +    loc: GeneratedSource, | 
|  | 188 | +  }; | 
|  | 189 | +  return { | 
|  | 190 | +    instructions: [loadLocalInstr, loadPropInstr], | 
|  | 191 | +    element: element, | 
|  | 192 | +  }; | 
|  | 193 | +} | 
|  | 194 | + | 
|  | 195 | +function emitSelectorFn(env: Environment, keys: Array<string>): Instruction { | 
|  | 196 | +  const obj: Place = { | 
|  | 197 | +    kind: 'Identifier', | 
|  | 198 | +    identifier: { | 
|  | 199 | +      id: makeIdentifierId(env.nextIdentifierId), | 
|  | 200 | +      name: makeIdentifierName('c'), | 
|  | 201 | +      mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)}, | 
|  | 202 | +      scope: null, | 
|  | 203 | +      type: makeType(), | 
|  | 204 | +      loc: GeneratedSource, | 
|  | 205 | +    }, | 
|  | 206 | +    effect: Effect.Unknown, | 
|  | 207 | +    reactive: false, | 
|  | 208 | +    loc: GeneratedSource, | 
|  | 209 | +  }; | 
|  | 210 | +  const instr: Array<Instruction> = []; | 
|  | 211 | +  const elements = []; | 
|  | 212 | +  for (const key of keys) { | 
|  | 213 | +    const {instructions, element: prop} = emitPropertyLoad(env, obj, key); | 
|  | 214 | +    instr.push(...instructions); | 
|  | 215 | +    elements.push(prop); | 
|  | 216 | +  } | 
|  | 217 | + | 
|  | 218 | +  const arrayInstr = emitArrayInstr(elements, env); | 
|  | 219 | +  instr.push(arrayInstr); | 
|  | 220 | + | 
|  | 221 | +  const block: BasicBlock = { | 
|  | 222 | +    kind: 'block', | 
|  | 223 | +    id: makeBlockId(0), | 
|  | 224 | +    instructions: instr, | 
|  | 225 | +    terminal: { | 
|  | 226 | +      id: makeInstructionId(0), | 
|  | 227 | +      kind: 'return', | 
|  | 228 | +      loc: GeneratedSource, | 
|  | 229 | +      value: arrayInstr.lvalue, | 
|  | 230 | +    }, | 
|  | 231 | +    preds: new Set(), | 
|  | 232 | +    phis: new Set(), | 
|  | 233 | +  }; | 
|  | 234 | + | 
|  | 235 | +  const fn: HIRFunction = { | 
|  | 236 | +    loc: GeneratedSource, | 
|  | 237 | +    id: null, | 
|  | 238 | +    fnType: 'Other', | 
|  | 239 | +    env, | 
|  | 240 | +    params: [obj], | 
|  | 241 | +    returnType: null, | 
|  | 242 | +    context: [], | 
|  | 243 | +    effects: null, | 
|  | 244 | +    body: { | 
|  | 245 | +      entry: block.id, | 
|  | 246 | +      blocks: new Map([[block.id, block]]), | 
|  | 247 | +    }, | 
|  | 248 | +    generator: false, | 
|  | 249 | +    async: false, | 
|  | 250 | +    directives: [], | 
|  | 251 | +  }; | 
|  | 252 | + | 
|  | 253 | +  reversePostorderBlocks(fn.body); | 
|  | 254 | +  removeUnreachableForUpdates(fn.body); | 
|  | 255 | +  removeDeadDoWhileStatements(fn.body); | 
|  | 256 | +  removeUnnecessaryTryCatch(fn.body); | 
|  | 257 | +  markInstructionIds(fn.body); | 
|  | 258 | +  mergeConsecutiveBlocks(fn); | 
|  | 259 | +  enterSSA(fn); | 
|  | 260 | +  inferTypes(fn); | 
|  | 261 | + | 
|  | 262 | +  const fnInstr: Instruction = { | 
|  | 263 | +    id: makeInstructionId(0), | 
|  | 264 | +    value: { | 
|  | 265 | +      kind: 'FunctionExpression', | 
|  | 266 | +      name: null, | 
|  | 267 | +      loweredFunc: { | 
|  | 268 | +        func: fn, | 
|  | 269 | +        dependencies: [], | 
|  | 270 | +      }, | 
|  | 271 | +      type: 'ArrowFunctionExpression', | 
|  | 272 | +      loc: GeneratedSource, | 
|  | 273 | +    }, | 
|  | 274 | +    lvalue: { | 
|  | 275 | +      kind: 'Identifier', | 
|  | 276 | +      identifier: makeTemporary(env.nextIdentifierId, GeneratedSource), | 
|  | 277 | +      effect: Effect.Unknown, | 
|  | 278 | +      reactive: false, | 
|  | 279 | +      loc: GeneratedSource, | 
|  | 280 | +    }, | 
|  | 281 | +    loc: GeneratedSource, | 
|  | 282 | +  }; | 
|  | 283 | +  return fnInstr; | 
|  | 284 | +} | 
|  | 285 | + | 
|  | 286 | +function emitArrayInstr(elements: Array<Place>, env: Environment): Instruction { | 
|  | 287 | +  const array: ArrayExpression = { | 
|  | 288 | +    kind: 'ArrayExpression', | 
|  | 289 | +    elements, | 
|  | 290 | +    loc: GeneratedSource, | 
|  | 291 | +  }; | 
|  | 292 | +  const arrayLvalue: Place = { | 
|  | 293 | +    kind: 'Identifier', | 
|  | 294 | +    identifier: makeTemporary(env.nextIdentifierId, GeneratedSource), | 
|  | 295 | +    effect: Effect.Unknown, | 
|  | 296 | +    reactive: false, | 
|  | 297 | +    loc: GeneratedSource, | 
|  | 298 | +  }; | 
|  | 299 | +  const arrayInstr: Instruction = { | 
|  | 300 | +    id: makeInstructionId(0), | 
|  | 301 | +    value: array, | 
|  | 302 | +    lvalue: arrayLvalue, | 
|  | 303 | +    loc: GeneratedSource, | 
|  | 304 | +  }; | 
|  | 305 | +  return arrayInstr; | 
|  | 306 | +} | 
0 commit comments