Closest assembly related to python bytecode
Asked Answered
J

1

0

I have the following basic python function:

def squared(num):
    if num < 2:
        print ('OK!')
    return num * num

Which produces the following bytecode:

>>> dis.dis(squared)
  2           0 LOAD_FAST                0 (num)
              3 LOAD_CONST               1 (2)
              6 COMPARE_OP               0 (<)
              9 POP_JUMP_IF_FALSE       20

  3          12 LOAD_CONST               2 ('OK!')
             15 PRINT_ITEM          
             16 PRINT_NEWLINE       
             17 JUMP_FORWARD             0 (to 20)

  4     >>   20 LOAD_FAST                0 (num)
             23 LOAD_FAST                0 (num)
             26 BINARY_MULTIPLY     
             27 RETURN_VALUE        

Most of the above look like mov and jmp-type operators. However, what do the following most closely mean in assembly?

LOAD_FAST, LOAD_CONST ?

What might be the closest assembly instructions for this?

Josiahjosias answered 15/10, 2019 at 1:12 Comment(1)
LOAD_FAST is accessing a local variable - this would be a move from a memory location (relative to the stack frame pointer), or possibly a move from a register (depending on the platform's standards, and the number of locals vs. the number of available registers). LOAD_CONST would generally be a move of a constant value. But note that trying to find a one-to-one equivalence between a high-level bytecode and an assembly language isn't going to get you very far: PRINT_ITEM, for example, is not something you're going to find as a single assembly opcode.Katlin
T
3

Python's bytecode is for a stack-based VM to simplify interpreters and waste less space on addresses (e.g. register numbers) by making them implicit. Loads are pushing onto that stack.

If you transliterated this in a very literal and braindead manner to an asm equivalent for fixed-width integers (unlike Python arbitrary precision integers), every LOAD_FAST might be a load from a local variable (in memory on the stack) into a register. But you'd still have to choose which registers, and real ISAs have a limited number of registers. But yes, LOAD_FAST is like a load.

Of course if you weren't intentionally being literal just for the sake of it, you'd know you already had num in a register and not load it again. So you'd use an instruction that read the same register twice, like imul eax, eax

And you'd have local variables living in registers when possible, not spilling them to the stack in the first place until you run out of registers.


If you want to learn about asm for CPUs, you can write an equivalent function in C and compile it (with optimization enabled, at least -Og if not -O2 or -O3) on the Godbolt compiler explorer: https://godbolt.org/. Or on your desktop, but Matt Godbolt wrote some nice filtering to remove noise and leave only the interesting parts. See also How to remove "noise" from GCC/clang assembly output?

Tumblebug answered 15/10, 2019 at 2:43 Comment(5)
Well, actually you can't easily do any of these transformations as Python is too dynamic. num can be object of any type so you can't just load its value into a register and then use a multiply instruction. If this were Python 3 you couldn't even assume that print was the builtin print function. It's hard to convert Python into assembly without essentially just unrolling the interpreter loop.Donegan
@RossRidge: Good point. I was imagining a translation that was real Python, treating num as a fixed-width integer, and was just doing the math parts. Probably should have just stuck to human-created analogy instead of bringing up compilers, but I wanted to say something about how braindead and literal the translation / transliteration was.Tumblebug
@RossRidge: updated to remove discussion of "compiling", and just talk about hand-crafted analogies, not implementing real Python.Tumblebug
@PeterCordes how does that eventually get compiled to bytecode? Is that also part of CPython, or does that happen somewhere else, for example with a gcc or something else?Josiahjosias
@David542: How does what get compiled to bytecode? I'm talking about writing (by hand in assembly) a function that takes an int arg and returns the square. (And mostly ignoring the part about I/O.) i.e. writing a function that does something similar to what the Python function does. Your question isn't very answerable, but I wanted to say something and this was too long for a comment.Tumblebug

© 2022 - 2024 — McMap. All rights reserved.