code_gen_options.rst 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. Code Generation Options
  2. =======================
  3. There are compiler flags to control code generation. They can be divided into two categories:
  4. * Optimizer passes are enabled by default and make the generated code more optimal.
  5. * Debugging options are not enabled by default, but they greatly improve
  6. developer experience, at the cost of increased gas and storage usage.
  7. Optimizer Passes
  8. ----------------
  9. Solang generates its own internal IR, before the LLVM IR is generated. This internal IR allows us to do
  10. several optimizations which LLVM cannot do, since it is not aware of higher-level language constructs.
  11. Arithmetic of large integers (larger than 64 bit) has special handling, since LLVM cannot generate them.
  12. So we need to do our own optimizations for these types, and we cannot rely on LLVM.
  13. .. _constant-folding:
  14. Constant Folding Pass
  15. +++++++++++++++++++++
  16. There is a constant folding (also called constant propagation) pass done, before all the other passes. This
  17. helps arithmetic of large types, and also means that the functions are constant folded when their arguments
  18. are constant. For example:
  19. .. code-block:: solidity
  20. bytes32 hash = keccak256('foobar');
  21. This is evaluated at compile time. You can see this in the Visual Studio Code extension by hover over `hash`;
  22. the hover will tell you the value of the hash.
  23. .. _strength-reduce:
  24. Strength Reduction Pass
  25. +++++++++++++++++++++++
  26. Strength reduction is when expensive arithmetic is replaced with cheaper ones. So far, the following types
  27. of arithmetic may be replaced:
  28. - 256 or 128 bit multiply maybe replaced by 64 bit multiply or shift
  29. - 256 or 128 bit divide maybe replaced by 64 bit divide or shift
  30. - 256 or 128 bit modulo maybe replaced by 64 bit modulo or bitwise and
  31. .. include:: ./examples/strength_reduce.sol
  32. :code: solidity
  33. Solang uses reaching definitions to track the known bits of the variables; here solang knows that i can have
  34. the values 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 and the other operand is always 100. So, the multiplication can be
  35. done using a single 64 bit multiply instruction. If you hover over the ``*`` in the Visual Studio Code you
  36. will see this noted.
  37. .. _dead-storage:
  38. Dead Storage pass
  39. +++++++++++++++++
  40. Loading from contract storage, or storing to contract storage is expensive. This optimization removes any
  41. redundant load from and store to contract storage. If the same variable is read twice, then the value from
  42. the first load is re-used. Similarly, if there is are two successive stores to the same variable, the first
  43. one is removed as it is redundant. For example:
  44. .. include:: ./examples/dead_storage_elimination.sol
  45. :code: solidity
  46. This optimization pass can be disabled by running `solang --no-dead-storage`. You can see the difference between
  47. having this optimization pass on by comparing the output of `solang --no-dead-storage --emit cfg foo.sol` with
  48. `solang --emit cfg foo.sol`.
  49. .. _vector-to-slice:
  50. Vector to Slice Pass
  51. ++++++++++++++++++++
  52. A `bytes` or `string` variable can be stored in a vector, which is a modifyable in-memory buffer, and a slice
  53. which is a pointer to readonly memory and an a length. Since a vector is modifyable, each instance requires
  54. a allocation. For example:
  55. .. include:: ./examples/vector_to_slice_optimization.sol
  56. :code: solidity
  57. This optimization pass can be disabled by running `solang --no-vector-to-slice`. You can see the difference between
  58. having this optimization pass on by comparing the output of `solang --no-vector-to-slice --emit cfg foo.sol` with
  59. `solang --emit cfg foo.sol`.
  60. .. _unused-variable-elimination:
  61. Unused Variable Elimination
  62. +++++++++++++++++++++++++++
  63. During the semantic analysis, Solang detects unused variables and raises warnings for them.
  64. During codegen, we remove all assignments that have been made to this unused variable. There is an example below:
  65. .. include:: ./examples/unused_variable_elimination.sol
  66. :code: solidity
  67. The variable 'x' will be removed from the function, as it has never been used. The removal won't affect any
  68. expressions inside the function.
  69. .. _common-subexpression-elimination:
  70. Common Subexpression Elimination
  71. ++++++++++++++++++++++++++++++++
  72. Solang performs common subexpression elimination by doing two passes over the CFG (Control
  73. Flow Graph). During the first one, it builds a graph to track existing expressions and detect repeated ones.
  74. During the second pass, it replaces the repeated expressions by a temporary variable, which assumes the value
  75. of the expression. To disable this feature, use `solang --no-cse`.
  76. Check out the example below. It contains multiple common subexpressions:
  77. .. include:: ./examples/common_subexpression_elimination.sol
  78. :code: solidity
  79. The expression `a*b` is repeated throughout the function and will be saved to a temporary variable.
  80. This temporary will be placed wherever there is an expression `a*b`. You can see the pass in action when you compile
  81. this contract and check the CFG, using `solang --emit cfg`.
  82. .. _Array-Bound-checks-optimizations:
  83. Array Bound checks optimization
  84. +++++++++++++++++++++++++++++++
  85. Whenever an array access is done, there must be a check for ensuring we are not accessing
  86. beyond the end of an array. Sometimes, the array length could be known. For example:
  87. .. include:: ./examples/array_bounds_check_optimization.sol
  88. :code: solidity
  89. In this example we access ``array`` element 1, while the array length is 3. So, no bounds
  90. checks are necessary and the code will more efficient if we do not emit the bounds check in
  91. the compiled contract.
  92. The array length is tracked in an invisible temporary variable, which is always kept up to date when, for example, a ``.pop()`` or ``.push()`` happens on the array
  93. or an assignment happens. Then, when the bounds check happens, rather than retrieving the array length from
  94. the array at runtime, bounds check becomes the constant expression `1 < 3` which is
  95. always true, so the check is omitted.
  96. This also means that, whenever the length of an array is accessed using '.length', it is replaced with a constant.
  97. Note that this optimization does not cover every case. When an array is passed
  98. as a function argument, for instance, the length is unknown.
  99. Debugging Options
  100. -----------------
  101. It may be desirable to have access to debug information regarding the contract execution.
  102. However, this will lead to more instructions as well as higher gas usage. Hence, it is disabled by default,
  103. but can be enabled using CLI flags. Just make sure not to use them in production deployments.
  104. .. _log-api-return-codes:
  105. Log runtime API call results
  106. ++++++++++++++++++++++++++++
  107. Runtime API calls are not guaranteed to succeed.
  108. By design, the low level results of these calls are abstracted away in Solidity.
  109. For development purposes, it can be desirable to observe the low level return code of such calls.
  110. The ``--log-api-return-codes`` flag will make the contract print the return code of runtime calls, if there are any.
  111. .. note::
  112. This is only implemented for the Substrate target.