summary refs log tree commit diff stats
path: root/cogs/solo.py
blob: 1652960884ea763c06203efaf68a9d022f20bbb7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from discord.ext.commands import GroupCog, Bot
from discord.ui import View, Button, Modal, button, TextInput
from discord.app_commands import command
from discord import Interaction, Embed, User, Member, ButtonStyle, Color
from httpx import AsyncClient
from nltk import sent_tokenize
from typing import Union, Optional
from common.types import question_category
from components.TossupButtons import SoloTossupButtons


class Solo(GroupCog, name="solo"):
    def __init__(self, bot: Bot) -> None:
        self.bot = bot
        super().__init__()

    @command(description="Do a tossup in solo mode (only you can control the tossup)")
    async def tossup(self, ctx: Interaction, category: Optional[question_category] = None):
        c = AsyncClient()
        params: dict = {'difficulties': [2,3,4,5]}
        if category is not None:
            params['categories'] = category

        req = await c.get(
            "https://qbreader.org/api/random-tossup", params=params
        )
        tossup: dict = req.json()["tossups"][0]
        tossup['sentences'] = await self.bot.loop.run_in_executor(None, sent_tokenize, tossup['question'])


        view: SoloTossupButtons = SoloTossupButtons(tossup, ctx.user)
        embed = Embed(title="Random Tossup", description=tossup["sentences"][0])
        embed.set_author(
            name=f"{tossup['set']['name']} Packet {tossup['packetNumber']} Question {tossup['questionNumber']}"
        )
        embed.set_footer(text="Questions obtained from qbreader.org")
        await ctx.response.send_message(embed=embed, view=view)
        await c.aclose()

async def setup(bot: Bot) -> None:
    await bot.add_cog(Solo(bot))
: int -> b : int [ ... ] Instructions have the following format: io1, io2, ... <- operation i1, i2, ... i1, i2 operands on the right hand side are immutable. io1, io2 are in-out operands. They're written to, and may also be read. User-defined functions will be called with the same syntax. They'll translate to a sequence of push instructions (one per operand, both in and in-out), a call instruction, and a sequence of pop instructions, either to a black hole (in operands) or a location (in-out operands). This follows the standard Unix calling convention. Each operand needs to be something push/pop can accept. Primitive operations depend on the underlying processor. We'd like each primitive operation supported by the language to map to a single instruction in the ISA. Sometimes we have to violate that (see below), but we definitely won't be writing to any temporary locations behind the scenes. The language affords control over registers, and tracking unused registers gets complex, and besides we may have no unused registers at a specific point. Instructions only modify their operands. In most ISAs, instructions operate on at most a word of data at a time. They also tend to not have more than 2-3 operands, and not modify more than 2 locations in memory. Since the number of reads from memory is limited, we break up complex high-level operations using a special type called `address`. Addresses are strictly short-term entities. They can't be stored in a compound type, and they can't be passed into or returned from a user-defined function. They also can't be used after a function call (because it could free the underlying memory) or label (because it gets complex to check control flow, and we want to translate each instruction simply and in isolation). === Compilation to 32-bit x86 Values can be stored: in code (literals) in registers on the stack on the global segment Variables on the stack are stored at *(ESP+n) Global variables are stored at *disp32, where disp32 is statically known Address variables have to be in a register. - You need them in a register to do a lookup, and - Saving them to even the stack increases the complexity of checks needed on function calls or labels. Compilation proceeds by pattern matching over an instruction along with knowledge about the types of its operands, as well as where they're stored (register/stack/global). We now enumerate mappings for various categories of instructions, based on the type and location of their operands. Where types of operands aren't mentioned below, all operands of an instruction should have the same (word-length) type. Lots of special cases because of limitations of the x86 ISA. Beware. A. x : int <- add y Requires y to be scalar. Result will always be an int. No pointer arithmetic. reg <- add literal => 81 0/subop 3/mod ...(0) reg <- add reg => 01 3/mod ...(1) reg <- add stack => 03 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 reg/r32 ...(2) reg <- add global => 03 0/mod 5/rm32/include-disp32 global/disp32 reg/r32 ...(3) stack <- add literal => 81 0/subop 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 literal/imm32 ...(4) stack <- add reg => 01 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 reg/r32 ...(5) stack <- add stack => disallowed stack <- add global => disallowed global <- add literal => 81 0/subop 0/mod 5/rm32/include-disp32 global/disp32 literal/imm32 ...(6) global <- add reg => 01 0/mod 5/rm32/include-disp32 global/disp32 reg/r32 ...(7) global <- add stack => disallowed global <- add global => disallowed Similarly for sub, and, or, xor and even copy. Replace the opcodes above with corresponding ones from this table: add sub and or xor copy/mov reg <- op literal 81 0/subop 81 5/subop 81 4/subop 81 1/subop 81 6/subop c7 reg <- op reg 01 or 03 29 or 2b 21 or 23 09 or 0b 31 or 33 89 or 8b reg <- op stack 03 2b 23 0b 33 8b reg <- op global 03 2b 23 0b 33 8b stack <- op literal 81 0/subop 81 5/subop 81 4/subop 81 1/subop 81 6/subop c7 stack <- op reg 01 29 21 09 31 89 global <- op literal 81 0/subop 81 5/subop 81 4/subop 81 1/subop 81 6/subop c7 global <- op reg 01 29 21 09 31 89 B. x/reg : int <- mul y Requires both y to be scalar. x must be in a register. Multiplies can't write to memory. reg <- mul literal => 69 ...(8) reg <- mul reg => 0f af 3/mod ...(9) reg <- mul stack => 0f af 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 reg/r32 ...(10) reg <- mul global => 0f af 0/mod 5/rm32/include-disp32 global/disp32 reg/r32 ...(11) C. x/EAX/quotient : int, y/EDX/remainder : int <- idiv z # divide EAX by z; store the result in EAX and EDX Requires source x and z to both be scalar. x must be in EAX and y must be in EDX. Divides can't write anywhere else. First clear EDX (we don't support ints larger than 32 bits): 31/xor 3/mod 2/rm32/EDX 2/r32/EDX then: EAX, EDX <- idiv literal => disallowed EAX, EDX <- idiv reg => f7 7/subop 3/mod ...(12) EAX, EDX <- idiv stack => f7 7/subop 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 ...(13) EAX, EDX <- idiv global => f7 7/subop 0/mod 5/rm32/include-disp32 global/disp32 reg/r32 ...(14) D. x : int <- not Requires x to be an int. reg <- not => f7 3/mod ...(15) stack <- not => f7 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 ...(16) global <- not => f7 0/mod 5/rm32/include-disp32 global/disp32 reg/r32 ...(17) E. x : (address t) <- get o : T, %f (Assumes T.f has type t.) o can't be on a register since it's a non-primitive (likely larger than a word) f is a literal x must be in a register (by definition for an address) below '*' works on either address or ref types For raw stack values we want to read *(ESP+n) For raw global values we want to read *disp32 For address stack values we want to read *(ESP+n)+ *(ESP+n) contains an address so we want to compute *(ESP+n) + literal reg1 <- get reg2, literal => 8d/lea 1/mod reg2/rm32 literal/disp8 reg1/r32 ...(18) reg <- get stack, literal => 8d/lea 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n+literal/disp8 reg/r32 ...(19) (simplifying assumption: stack frames can't be larger than 256 bytes) reg <- get global, literal => 8d/lea 0/mod 5/rm32/include-disp32 global+literal/disp32, reg/r32 ...(20) F. x : (offset T) <- index i : int, %size(T) reg1 <- index reg2, literal => 69/mul 3/mod reg2/rm32 literal/imm32 -> reg1/r32 or 68/mul 3/mod reg2/rm32 literal/imm8 -> reg1/r32 ...(21) reg1 <- index stack, literal => 69/mul 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n/disp8 literal/imm32 -> reg1/r32 ...(22) reg1 <- index global, literal => 69/mul 0/mod 5/rm32/include-disp32 global/disp32 literal/imm32 -> reg1/r32 ...(23) optimization: avoid multiply if literal is a power of 2 use SIB byte if literal is 2, 4 or 8 or left shift G. x : (address T) <- advance o : (array T), idx : (offset T) reg <- advance a/reg, idx/reg => 8d/lea 0/mod 4/rm32/SIB a/base idx/index 0/scale reg/r32 ...(24) reg <- advance stack, literal => 8d/lea 1/mod 4/rm32/SIB 4/base/ESP 4/index/none 0/scale n+literal/disp8 reg/r32 ...(25) reg <- advance stack, reg2 => 8d/lea 1/mod 4/rm32/SIB 4/base/ESP reg2/index 0/scale n/disp8 reg/r32 ...(26) reg <- advance global, literal => 8d/lea 0/mod 5/rm32/include-disp32 global+literal/disp32, reg/r32 ...(27) also instructions for runtime bounds checking === Example Putting it all together: code generation for `a[i].y = 4` where a is an array of 2-d points with x, y coordinates. If a is allocated on the stack, say of type (array point 6) at (ESP+4): offset/EAX : (offset point) <- index i, 8 # (22) tmp/EBX : (address point) <- advance a : (array point 6), offset/EAX # (26) tmp2/ECX : (address number) <- get tmp/EBX : (address point), 4/y # (18) *tmp2/ECX <- copy 4 # (5 for copy/mov with 0 disp8) Many instructions, particularly variants of 'get' and 'advance' -- end up encoding the exact same instructions. But the types differ, and the type-checker checks them differently. === Advanced checks Couple of items require inserting mapping to multiple instructions: bounds checking against array length in 'advance' dereferencing 'ref' types (see type list up top) A. Dereferencing a ref tmp/EDX <- advance *s, tmp0/EDI => compare (ESP+4), *(ESP+8) ; '*' from compiler2 jump-unless-equal panic EDX <- add ESP, 8 EDX <- copy *EDX EDX <- add EDX, 4 EDX <- 8d/lea EDX + result === More speculative ideas Initialize data segment with special extensible syntax for literals. All literals except numbers and strings start with %. %size(type) => compiler replaces with size of type %point(3, 4) => two words and so on. === Credits Forth C Rust Lisp qhasm