Allow specifying custom objdump flags (#427)

* Trying to add a new section

* objdump section is no longer borken

* objdump section is a bit more independent from the compilerargs one

* add objdump_flags to database

* Allow to save objdump flags on change

* Actually pass the objdump flags to objdump

* Allow adding objdump flags to presets

* add short description to compilers.json

* filter_objdump_flags

* Introduce GCCMIPSCompiler class

* Add -M gekko / broadway by default for gc/wii compilers

* format autogenerated file

* type annotation

* Disable enum trailing comma warnings on Majora's Mask

* wip

* flags as array, backend parsing, frontend cleanup

* style scratch options

* loose ends

* only show diff opts section if there are any options to show

* fix mypy

Co-authored-by: Ethan Roseman <ethteck@gmail.com>
Co-authored-by: Alex Bates <alex@nanaian.town>
This commit is contained in:
Anghelo Carvajal
2022-04-05 09:19:04 -04:00
committed by GitHub
parent 4aa034343a
commit 231842b545
23 changed files with 337 additions and 239 deletions

View File

@@ -1,6 +1,6 @@
from functools import cache
import logging
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path
from typing import ClassVar, Dict, List, Optional, OrderedDict
@@ -11,7 +11,6 @@ from coreapp.flags import (
COMMON_IDO_FLAGS,
COMMON_MWCC_FLAGS,
COMMON_GCC_PS1_FLAGS,
FlagSet,
Flags,
)
@@ -49,14 +48,16 @@ class Compiler:
@dataclass(frozen=True)
class Preset:
name: str
flags: str
compiler: Compiler
flags: str
diff_flags: list[str] = field(default_factory=list)
def to_json(self) -> Dict[str, str]:
def to_json(self) -> Dict[str, object]:
return {
"name": self.name,
"flags": self.flags,
"compiler": self.compiler.id,
"flags": self.flags,
"diff_flags": self.diff_flags,
}
@@ -553,128 +554,133 @@ _all_presets = [
# GBA
Preset(
"Rhythm Tengoku",
"-mthumb-interwork -Wparentheses -O2 -fhex-asm",
AGBCC,
"-mthumb-interwork -Wparentheses -O2 -fhex-asm",
),
Preset(
"The Minish Cap",
"-O2 -Wimplicit -Wparentheses -Werror -Wno-multichar -g3",
AGBCC,
"-O2 -Wimplicit -Wparentheses -Werror -Wno-multichar -g3",
),
Preset(
"Mother 3",
"-fno-exceptions -fno-rtti -fhex-asm -mthumb-interwork -Wimplicit -Wparentheses -O2 -g3",
AGBCCPP,
"-fno-exceptions -fno-rtti -fhex-asm -mthumb-interwork -Wimplicit -Wparentheses -O2 -g3",
),
# Switch
Preset(
"Super Mario Odyssey",
"-x c++ -O3 -g2 -std=c++1z -fno-rtti -fno-exceptions -Wall -Wextra -Wdeprecated -Wno-unused-parameter -Wno-unused-private-field -fno-strict-aliasing -Wno-invalid-offsetof -D SWITCH -D NNSDK -D MATCHING_HACK_NX_CLANG",
CLANG_391,
"-x c++ -O3 -g2 -std=c++1z -fno-rtti -fno-exceptions -Wall -Wextra -Wdeprecated -Wno-unused-parameter -Wno-unused-private-field -fno-strict-aliasing -Wno-invalid-offsetof -D SWITCH -D NNSDK -D MATCHING_HACK_NX_CLANG",
),
Preset(
"Breath of the Wild",
"-x c++ -O3 -g2 -std=c++1z -fno-rtti -fno-exceptions -Wall -Wextra -Wdeprecated -Wno-unused-parameter -Wno-unused-private-field -fno-strict-aliasing -Wno-invalid-offsetof -D SWITCH -D NNSDK -D MATCHING_HACK_NX_CLANG",
CLANG_401,
"-x c++ -O3 -g2 -std=c++1z -fno-rtti -fno-exceptions -Wall -Wextra -Wdeprecated -Wno-unused-parameter -Wno-unused-private-field -fno-strict-aliasing -Wno-invalid-offsetof -D SWITCH -D NNSDK -D MATCHING_HACK_NX_CLANG",
),
# PS1
Preset(
"Castlevania: Symphony of the Night",
"-O2 -G0 -funsigned-char",
GCC263_MIPSEL,
"-O2 -G0 -funsigned-char",
),
Preset(
"Evo's Sapce Adventures",
"-O2",
PSYQ46,
"-O2",
),
# N64
Preset("Super Mario 64", "-O1 -g -mips2", IDO53),
Preset("Mario Kart 64", "-O2 -mips2", IDO53),
Preset("GoldenEye / Perfect Dark", "-Olimit 2000 -mips2 -O2", IDO53),
Preset("Diddy Kong Racing", "-O2 -mips1", IDO53),
Preset("Dinosaur Planet", "-O2 -g3 -mips2", IDO53),
Preset("Dinosaur Planet (DLLs)", "-O2 -g3 -mips2 -KPIC", IDO53),
Preset("Ocarina of Time", "-O2 -mips2", IDO71),
Preset("Majora's Mask", "-O2 -g3 -mips2", IDO71),
Preset("Mario Party 3", "-O1 -mips2", GCC272KMC),
Preset("Paper Mario", "-O2 -fforce-addr", GCC281),
Preset("Super Mario 64", IDO53, "-O1 -g -mips2"),
Preset("Mario Kart 64", IDO53, "-O2 -mips2"),
Preset("GoldenEye / Perfect Dark", IDO53, "-Olimit 2000 -mips2 -O2"),
Preset("Diddy Kong Racing", IDO53, "-O2 -mips1"),
Preset("Dinosaur Planet", IDO53, "-O2 -g3 -mips2"),
Preset("Dinosaur Planet (DLLs)", IDO53, "-O2 -g3 -mips2 -KPIC"),
Preset("Ocarina of Time", IDO71, "-O2 -mips2"),
Preset(
"Majora's Mask",
IDO71,
"-O2 -g3 -mips2 -woff 624",
diff_flags=["-Mreg-names=32"],
),
Preset("Mario Party 3", GCC272KMC, "-O1 -mips2"),
Preset("Paper Mario", GCC281, "-O2 -fforce-addr"),
# GC_WII
Preset(
"Super Monkey Ball",
"-O4,p -nodefaults -fp hard -Cpp_exceptions off -enum int -inline auto",
MWCC_233_159,
"-O4,p -nodefaults -fp hard -Cpp_exceptions off -enum int -inline auto",
),
Preset(
"Super Mario Sunshine",
"-lang=c++ -Cpp_exceptions off -fp hard -O4 -nodefaults -enum int -rostr",
MWCC_233_163,
"-lang=c++ -Cpp_exceptions off -fp hard -O4 -nodefaults -enum int -rostr",
),
Preset(
"Pikmin",
"-lang=c++ -nodefaults -Cpp_exceptions off -RTTI on -fp hard -O4,p",
MWCC_233_163E,
"-lang=c++ -nodefaults -Cpp_exceptions off -RTTI on -fp hard -O4,p",
),
Preset(
"Super Smash Bros. Melee",
"-O4,p -nodefaults -fp hard -Cpp_exceptions off -enum int -fp_contract on -inline auto",
MWCC_233_163E,
"-O4,p -nodefaults -fp hard -Cpp_exceptions off -enum int -fp_contract on -inline auto",
),
Preset(
"Battle for Bikini Bottom",
"-lang=c++ -g -Cpp_exceptions off -RTTI off -fp hard -fp_contract on -O4,p -maxerrors 1 -str reuse,pool,readonly -char unsigned -enum int -use_lmw_stmw on -inline off",
MWCC_247_92,
"-lang=c++ -g -Cpp_exceptions off -RTTI off -fp hard -fp_contract on -O4,p -maxerrors 1 -str reuse,pool,readonly -char unsigned -enum int -use_lmw_stmw on -inline off",
),
Preset(
"Pikmin 2",
"-lang=c++ -nodefaults -Cpp_exceptions off -RTTI off -fp hard -fp_contract on -rostr -O4,p -use_lmw_stmw on -enum int -inline auto -sdata 8 -sdata2 8",
MWCC_247_107,
"-lang=c++ -nodefaults -Cpp_exceptions off -RTTI off -fp hard -fp_contract on -rostr -O4,p -use_lmw_stmw on -enum int -inline auto -sdata 8 -sdata2 8",
),
Preset(
"The Thousand-Year Door",
"-fp hard -fp_contract on -enum int -O4,p -sdata 48 -sdata2 6 -rostr -multibyte -use_lmw_stmw on -inline deferred -Cpp_exceptions off",
MWCC_247_108,
"-fp hard -fp_contract on -enum int -O4,p -sdata 48 -sdata2 6 -rostr -multibyte -use_lmw_stmw on -inline deferred -Cpp_exceptions off",
),
Preset(
"Twilight Princess",
"-lang=c++ -Cpp_exceptions off -nodefaults -O3 -fp hard -msgstyle gcc -str pool,readonly,reuse -RTTI off -maxerrors 1 -enum int",
MWCC_247_108,
"-lang=c++ -Cpp_exceptions off -nodefaults -O3 -fp hard -msgstyle gcc -str pool,readonly,reuse -RTTI off -maxerrors 1 -enum int",
),
Preset(
"Super Paper Mario (DOL)",
"-lang=c99 -enc SJIS -fp hard -O4 -use_lmw_stmw on -str pool -rostr -inline all -sdata 4 -sdata2 4",
MWCC_41_60831,
"-lang=c99 -enc SJIS -fp hard -O4 -use_lmw_stmw on -str pool -rostr -inline all -sdata 4 -sdata2 4",
),
Preset(
"Super Paper Mario (REL)",
"-lang=c99 -enc SJIS -fp hard -O4 -use_lmw_stmw on -str pool -rostr -ipa file -sdata 0 -sdata2 0 -pool off -ordered-fp-compares",
MWCC_41_60831,
"-lang=c99 -enc SJIS -fp hard -O4 -use_lmw_stmw on -str pool -rostr -ipa file -sdata 0 -sdata2 0 -pool off -ordered-fp-compares",
),
Preset(
"Wii Sports",
"-lang=c++ -enum int -inline auto -Cpp_exceptions off -RTTI off -fp hard -O4,p -nodefaults",
MWCC_41_60831,
"-lang=c++ -enum int -inline auto -Cpp_exceptions off -RTTI off -fp hard -O4,p -nodefaults",
),
Preset(
"Super Mario Galaxy",
"-Cpp_exceptions off -stdinc -nodefaults -fp hard -lang=c++ -inline auto,level=2 -ipa file -O4,s -rtti off -sdata 4 -sdata2 4 -enum int",
MWCC_41_60126,
"-Cpp_exceptions off -stdinc -nodefaults -fp hard -lang=c++ -inline auto,level=2 -ipa file -O4,s -rtti off -sdata 4 -sdata2 4 -enum int",
),
Preset(
"Mario Party 4",
MWCC_242_81,
"-O0,p -str pool -fp hard -Cpp_exceptions off",
),
# NDS
Preset(
"Pokémon Diamond / Pearl",
"-O4,p -gccext,on -fp soft -lang c99 -Cpp_exceptions off -interworking -enum int",
MWCC_30_123,
"-O4,p -gccext,on -fp soft -lang c99 -Cpp_exceptions off -interworking -enum int",
),
Preset(
"Pokémon HeartGold / SoulSilver",
"-O4,p -enum int -lang c99 -Cpp_exceptions off -gccext,on -gccinc -interworking -gccdep -MD",
MWCC_30_137,
),
Preset(
"Mario Party 4",
"-O0,p -str pool -fp hard -Cpp_exceptions off",
MWCC_242_81,
"-O4,p -enum int -lang c99 -Cpp_exceptions off -gccext,on -gccinc -interworking -gccdep -MD",
),
]

View File

@@ -19,7 +19,35 @@ logger = logging.getLogger(__name__)
MAX_FUNC_SIZE_LINES = 5000
class AsmDifferWrapper:
class DiffWrapper:
@staticmethod
def filter_objdump_flags(compiler_flags: str) -> str:
# Remove irrelevant flags that are part of the base objdump configs, but clutter the compiler settings field.
# TODO: use cfg for this?
skip_flags_with_args: set[str] = set()
skip_flags = {
"--disassemble",
"--disassemble-zeroes",
"--line-numbers",
"--reloc",
}
skip_next = False
flags = []
for flag in compiler_flags.split():
if skip_next:
skip_next = False
continue
if flag in skip_flags:
continue
if flag in skip_flags_with_args:
skip_next = True
continue
if any(flag.startswith(f) for f in skip_flags_with_args):
continue
flags.append(flag)
return " ".join(flags)
@staticmethod
def create_config(arch: asm_differ.ArchSettings) -> asm_differ.Config:
return asm_differ.Config(
@@ -84,14 +112,24 @@ class AsmDifferWrapper:
return ["--start-address=0"]
@staticmethod
def parse_objdump_flags(diff_flags: list[str]) -> list[str]:
ret = []
if "-Mreg-names=32" in diff_flags:
ret.append("-Mreg-names=32")
return ret
@staticmethod
def run_objdump(
target_data: bytes,
platform: Platform,
config: asm_differ.Config,
label: Optional[str],
flags: list[str],
) -> str:
flags = [
flags += [
"--disassemble",
"--disassemble-zeroes",
"--line-numbers",
@@ -102,15 +140,16 @@ class AsmDifferWrapper:
target_path = sandbox.path / "out.s"
target_path.write_bytes(target_data)
flags += AsmDifferWrapper.get_objdump_target_function_flags(
flags += DiffWrapper.get_objdump_target_function_flags(
sandbox, target_path, platform, label
)
flags += config.arch.arch_flags
if platform.objdump_cmd:
try:
objdump_proc = sandbox.run_subprocess(
[platform.objdump_cmd]
+ config.arch.arch_flags
platform.objdump_cmd.split()
+ flags
+ [sandbox.rewrite_path(target_path)],
shell=True,
@@ -132,13 +171,14 @@ class AsmDifferWrapper:
platform: Platform,
diff_label: Optional[str],
config: asm_differ.Config,
diff_flags: list[str],
) -> str:
if len(elf_object) == 0:
raise AssemblyError("Asm empty")
basedump = AsmDifferWrapper.run_objdump(
elf_object, platform, config, diff_label
basedump = DiffWrapper.run_objdump(
elf_object, platform, config, diff_label, diff_flags
)
if not basedump:
raise ObjdumpError("Error running objdump")
@@ -162,7 +202,8 @@ class AsmDifferWrapper:
platform: Platform,
diff_label: Optional[str],
compiled_elf: bytes,
allow_target_only: bool = False,
allow_target_only: bool,
diff_flags: list[str],
) -> DiffResult:
if platform == DUMMY:
@@ -175,14 +216,20 @@ class AsmDifferWrapper:
logger.error(f"Unsupported arch: {platform.arch}. Continuing assuming mips")
arch = asm_differ.get_arch("mips")
config = AsmDifferWrapper.create_config(arch)
objdump_flags = DiffWrapper.parse_objdump_flags(diff_flags)
basedump = AsmDifferWrapper.get_dump(
bytes(target_assembly.elf_object), platform, diff_label, config
config = DiffWrapper.create_config(arch) # TODO pass and use diff_flags
basedump = DiffWrapper.get_dump(
bytes(target_assembly.elf_object),
platform,
diff_label,
config,
objdump_flags,
)
try:
mydump = AsmDifferWrapper.get_dump(
compiled_elf, platform, diff_label, config
mydump = DiffWrapper.get_dump(
compiled_elf, platform, diff_label, config, objdump_flags
)
except Exception as e:
if allow_target_only:

View File

@@ -72,6 +72,10 @@ COMMON_IDO_FLAGS: Flags = [
Checkbox("kpic", "-KPIC"),
]
COMMON_MIPS_DIFF_FLAGS: Flags = [
Checkbox("mreg_names=32", "-Mreg-names=32"),
]
COMMON_MWCC_FLAGS: Flags = [
FlagSet(
id="mwcc_opt_level",

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.3 on 2022-04-05 09:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("coreapp", "0019_scratch_preset"),
]
operations = [
migrations.AddField(
model_name="compilerconfig",
name="diff_flags",
field=models.JSONField(blank=True, default=str),
),
migrations.AddField(
model_name="scratch",
name="diff_flags",
field=models.JSONField(blank=True, default=str),
),
]

View File

@@ -224,6 +224,7 @@ class ProjectFunction(models.Model):
"platform": compiler_config.platform,
"compiler": compiler_config.compiler,
"compiler_flags": compiler_config.compiler_flags,
"diff_flags": compiler_config.diff_flags,
},
allow_project=True,
)

View File

@@ -38,6 +38,7 @@ class CompilerConfig(models.Model):
compiler = models.CharField(max_length=100)
platform = models.CharField(max_length=100)
compiler_flags = models.TextField(max_length=1000, default="", blank=True)
diff_flags = models.JSONField(default=str, blank=True)
class Scratch(models.Model):
@@ -53,6 +54,9 @@ class Scratch(models.Model):
compiler_flags = models.TextField(
max_length=1000, default="", blank=True
) # TODO: reference a CompilerConfig
diff_flags = models.JSONField(
default=str, blank=True
) # TODO: reference a CompilerConfig
preset = models.CharField(max_length=100, blank=True, null=True)
target_assembly = models.ForeignKey(Assembly, on_delete=models.CASCADE)
source_code = models.TextField(blank=True)

View File

@@ -1,6 +1,8 @@
import logging
from dataclasses import dataclass
from typing import OrderedDict
from dataclasses import dataclass, field
from typing import ClassVar, OrderedDict
from coreapp.flags import COMMON_MIPS_DIFF_FLAGS, Flags
logger = logging.getLogger(__name__)
@@ -16,7 +18,8 @@ class Platform:
objdump_cmd: str
nm_cmd: str
asm_prelude: str
supports_objdump_disassemble: bool = False
diff_flags: Flags = field(default_factory=list, hash=False)
supports_objdump_disassemble: bool = False # TODO turn into objdump flag
def from_id(platform_id: str) -> Platform:
@@ -56,6 +59,7 @@ N64 = Platform(
assemble_cmd='mips-linux-gnu-as -march=vr4300 -mabi=32 -o "$OUTPUT" "$INPUT"',
objdump_cmd="mips-linux-gnu-objdump",
nm_cmd="mips-linux-gnu-nm",
diff_flags=COMMON_MIPS_DIFF_FLAGS,
asm_prelude="""
.macro .late_rodata
.section .rodata
@@ -171,7 +175,7 @@ GC_WII = Platform(
description="PowerPC",
arch="ppc",
assemble_cmd='powerpc-eabi-as -mgekko -o "$OUTPUT" "$INPUT"',
objdump_cmd="powerpc-eabi-objdump",
objdump_cmd="powerpc-eabi-objdump -M broadway",
nm_cmd="powerpc-eabi-nm",
asm_prelude="""
.macro glabel label

View File

@@ -110,6 +110,7 @@ class ScratchCreateSerializer(serializers.Serializer[None]):
compiler = serializers.CharField(allow_blank=True, required=True)
platform = serializers.CharField(allow_blank=True, required=False)
compiler_flags = serializers.CharField(allow_blank=True, required=False)
diff_flags = serializers.CharField(allow_blank=True, required=False)
preset = serializers.CharField(allow_blank=True, required=False)
source_code = serializers.CharField(allow_blank=True, required=False)
target_asm = serializers.CharField(allow_blank=True)

View File

@@ -410,6 +410,52 @@ class CompilationTests(BaseTestCase):
"The compilation result should be different",
)
@requiresCompiler(IDO71)
def test_fpr_reg_names_output(self):
"""
Ensure that we can view fpr reg names by passing the appropriate diff flag
"""
scratch_dict = {
"platform": N64.id,
"compiler": IDO71.id,
"diff_flags": "[-Mreg-names=32]",
"context": "",
"target_asm": """
glabel test
lui $at, 0x3ff0
mtc1 $at, $fv1f
mtc1 $zero, $fv1
beqz $a0, .L00400194
move $v0, $a0
andi $a1, $a0, 3
negu $a1, $a1
beqz $a1, .L004000EC
addu $v1, $a1, $a0
mtc1 $v0, $ft0
nop
""",
}
scratch = self.create_scratch(scratch_dict)
# Test that we can compile a scratch
response = self.client.post(
reverse("scratch-compile", kwargs={"pk": scratch.slug})
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.json()["errors"]), 0)
# Confirm the output contains the expected fpr reg names
self.assertTrue("fv1f" in str(response.json()))
response = self.client.post(
reverse("scratch-compile", kwargs={"pk": scratch.slug}),
{"diff_flags": "[]"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.json()["errors"]), 0)
# Confirm the output does not contain the expected fpr reg names
self.assertFalse("fv1f" in str(response.json()))
@requiresCompiler(MWCC_247_92)
def test_mwcc_wine(self):
"""

View File

@@ -18,6 +18,7 @@ class CompilersDetail(APIView):
c.id: {
"platform": c.platform.id,
"flags": [f.to_json() for f in c.flags],
"diff_flags": [f.to_json() for f in c.platform.diff_flags],
}
for c in compilers.available_compilers()
}

View File

@@ -19,7 +19,7 @@ from rest_framework.viewsets import GenericViewSet
from coreapp import compilers, platforms
from ..asm_diff_wrapper import AsmDifferWrapper
from ..diff_wrapper import DiffWrapper
from ..compiler_wrapper import CompilationResult, CompilerWrapper, DiffResult
from ..decompiler_wrapper import DecompilerWrapper
@@ -68,12 +68,13 @@ def compile_scratch(scratch: Scratch) -> CompilationResult:
def diff_compilation(
scratch: Scratch, compilation: CompilationResult, allow_target_only: bool = False
) -> DiffResult:
return AsmDifferWrapper.diff(
return DiffWrapper.diff(
scratch.target_assembly,
platforms.from_id(scratch.platform),
scratch.diff_label,
compilation.elf_object,
allow_target_only=allow_target_only,
diff_flags=scratch.diff_flags,
)
@@ -156,7 +157,13 @@ def family_etag(request: Request, pk: Optional[str] = None) -> Optional[str]:
def update_needs_recompile(partial: Dict[str, Any]) -> bool:
recompile_params = ["compiler", "compiler_flags", "source_code", "context"]
recompile_params = [
"compiler",
"compiler_flags",
"diff_flags",
"source_code",
"context",
]
for param in recompile_params:
if param in partial:
@@ -213,6 +220,9 @@ def create_scratch(data: Dict[str, Any], allow_project=False) -> Scratch:
compiler_flags = data.get("compiler_flags", "")
compiler_flags = CompilerWrapper.filter_compiler_flags(compiler_flags)
diff_flags = data.get("diff_flags", "")
diff_flags = DiffWrapper.filter_objdump_flags(diff_flags)
preset = data.get("preset", "")
if preset and not compilers.preset_from_name(preset):
raise serializers.ValidationError("Unknown preset:" + preset)
@@ -246,6 +256,7 @@ def create_scratch(data: Dict[str, Any], allow_project=False) -> Scratch:
"name": name,
"compiler": compiler.id,
"compiler_flags": compiler_flags,
"diff_flags": diff_flags,
"preset": preset,
"context": context,
"diff_label": diff_label,
@@ -337,6 +348,8 @@ class ScratchViewSet(
scratch.compiler = request.data["compiler"]
if "compiler_flags" in request.data:
scratch.compiler_flags = request.data["compiler_flags"]
if "diff_flags" in request.data:
scratch.diff_flags = request.data["diff_flags"]
if "source_code" in request.data:
scratch.source_code = request.data["source_code"]
if "context" in request.data:

View File

@@ -188,5 +188,6 @@
"sdata_limit": "Small data limit",
"sdata_limit.-G0": "0 bytes",
"sdata_limit.-G4": "4 bytes",
"sdata_limit.-G8": "8 bytes"
"sdata_limit.-G8": "8 bytes",
"mreg_names=32": "ABI FPR names"
}

View File

@@ -1,9 +0,0 @@
.chooseACompiler {
display: flex;
flex-direction: column;
}
.chooseACompilerActions {
padding: 1em;
align-self: center;
}

View File

@@ -1,30 +0,0 @@
import { useState } from "react"
import { ArrowRightIcon } from "@primer/octicons-react"
import Button from "../Button"
import CompilerOpts, { CompilerOptsT } from "../compiler/CompilerOpts"
import styles from "./ChooseACompiler.module.scss"
export default function ChooseACompiler({ platform, onCommit }: {
platform: string
onCommit: (opts: CompilerOptsT) => void
}) {
const [compiler, setCompiler] = useState<CompilerOptsT>()
return <div className={styles.chooseACompiler}>
<CompilerOpts
platform={platform}
value={compiler}
onChange={c => setCompiler(c)}
/>
<div className={styles.chooseACompilerActions}>
<Button primary onClick={() => onCommit(compiler)}>
Use this compiler
<ArrowRightIcon size={16} />
</Button>
</div>
</div>
}

View File

@@ -2,11 +2,8 @@ import { Dispatch, JSXElementConstructor, ReactElement, RefObject, SetStateActio
import * as resizer from "react-simple-resizer"
import { Scratch } from "../../lib/api"
import { CompilerOptsT } from "../compiler/CompilerOpts"
import Tabs, { Tab } from "../Tabs"
import ChooseACompiler from "./ChooseACompiler"
import styles from "./ScratchBody.module.scss"
const LEFT_PANE_MIN_WIDTH = 100
@@ -21,8 +18,6 @@ export type Props = {
setRightTab: Dispatch<SetStateAction<string>>
leftTabs: ReactElement<typeof Tab, string | JSXElementConstructor<unknown>>[]
rightTabs: ReactElement<typeof Tab, string | JSXElementConstructor<unknown>>[]
setCompilerOpts: ({ compiler, compiler_flags }: CompilerOptsT) => void
scratch: Scratch
}
export default function ScratchBody({
@@ -33,8 +28,6 @@ export default function ScratchBody({
setRightTab,
leftTabs,
rightTabs,
setCompilerOpts,
scratch,
}: Props) {
return container.width > TWO_PANE_MIN_CONTAINER_WIDTH
? (<resizer.Container className={styles.resizer}>
@@ -56,18 +49,13 @@ export default function ScratchBody({
/>
<resizer.Section className={styles.diffSection} minSize={RIGHT_PANE_MIN_WIDTH}>
{scratch.compiler === ""
? <ChooseACompiler platform={scratch.platform} onCommit={setCompilerOpts} />
: <Tabs activeTab={rightTab} onChange={setRightTab} background="var(--g300)" border={false}>
{rightTabs}
</Tabs>
}
{<Tabs activeTab={rightTab} onChange={setRightTab} background="var(--g300)" border={false}>
{rightTabs}
</Tabs>}
</resizer.Section>
</resizer.Container>)
: scratch.compiler === ""
? (<ChooseACompiler platform={scratch.platform} onCommit={setCompilerOpts} />)
: (<Tabs activeTab={leftTab} onChange={setLeftTab} background="var(--g300)" border={false}>
{leftTabs}
{rightTabs}
</Tabs>)
: (<Tabs activeTab={leftTab} onChange={setLeftTab} background="var(--g300)" border={false}>
{leftTabs}
{rightTabs}
</Tabs>)
}

View File

@@ -105,7 +105,7 @@ export function useLeftTabs({ scratch, setScratch, setSelectedSourceLine }: {
</Tab>
),
[LeftScratchTab.COMPILER_OPTS]: (
<Tab key="compiler_opts" tabKey="compiler_opts" label="Compiler options" className={styles.compilerOptsTab}>
<Tab key="compiler_opts" tabKey="compiler_opts" label="Scratch options" className={styles.compilerOptsTab}>
<div className={styles.compilerOptsContainer}>
<CompilerOpts
platform={scratch.platform}

View File

@@ -1,8 +0,0 @@
.popover {
border: 1px solid var(--g500);
border-radius: 1em;
box-shadow: 0 2px 8px 0 #0008;
max-width: 50em;
z-index: 999;
}

View File

@@ -1,71 +0,0 @@
import { useState } from "react"
import { CpuIcon } from "@primer/octicons-react"
import { motion, AnimatePresence } from "framer-motion"
import { useLayer, Arrow } from "react-laag"
import { useThemeVariable } from "../../lib/hooks"
import Button from "../Button"
import styles from "./CompilerButton.module.css"
import CompilerOpts, { Props as CompilerOptsProps } from "./CompilerOpts"
export type Props = {
platform: CompilerOptsProps["platform"]
value: CompilerOptsProps["value"]
onChange: CompilerOptsProps["onChange"]
disabled?: boolean
}
export default function CompilerButton({ platform, value, onChange, disabled }: Props) {
const [isOpen, setOpen] = useState(false)
const arrowColor = useThemeVariable("--g300")
const arrowBorderColor = useThemeVariable("--g500")
const close = () => setOpen(false)
const { renderLayer, triggerProps, layerProps, arrowProps } = useLayer({
isOpen,
onOutsideClick: close,
overflowContainer: false,
auto: true,
placement: "bottom-end",
triggerOffset: 14,
})
return <>
<Button
{...triggerProps}
onClick={() => {
if (!disabled)
setOpen(!isOpen)
}}
disabled={disabled}
>
<CpuIcon size={16} />
Compiler...
</Button>
{renderLayer(
<AnimatePresence>
{isOpen && <motion.div
className={styles.popover}
initial={{ y: -50, scaleX: 0.7, scaleY: 0, opacity: 0 }}
animate={{ y: 0, scaleX: 1, scaleY: 1, opacity: 1 }}
exit={{ y: -50, scaleX: 0.7, scaleY: 0, opacity: 0 }}
transition={{ type: "spring", duration: 0.3 }}
{...layerProps}
>
<CompilerOpts isPopup={true} platform={platform} value={value} onChange={onChange} />
<Arrow
size={12}
backgroundColor={arrowColor}
borderWidth={1}
borderColor={arrowBorderColor}
{...arrowProps}
/>
</motion.div>}
</AnimatePresence>
)}
</>
}

View File

@@ -1,7 +1,7 @@
.header {
padding: 1.5em;
padding-bottom: 0;
background: var(--g300);
background: transparent;
display: flex;
flex-direction: row;
@@ -14,19 +14,27 @@
.header > * {
flex-shrink: 0;
border-radius: 0;
}
.container {
.section {
padding: 1.5em;
background: var(--g300);
}
.header:not([data-is-popup]),
.container:not([data-is-popup]) {
border-radius: 0;
background: transparent;
}
/* border-bottom: 1px solid var(--g400); */
.section:not(:last-child) {
border-bottom: 1px solid var(--a100);
}
.heading {
font-size: 1.1em;
font-weight: 500;
padding: 0.5em;
padding-top: 0;
color: var(--g1200);
}
.preset {
@@ -82,6 +90,13 @@
margin-top: 1em;
}
.diffFlags {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
margin-top: 1em;
}
.flag {
flex-grow: 1;
display: inline-block;

View File

@@ -6,9 +6,9 @@ import * as api from "../../lib/api"
import PlatformIcon from "../PlatformSelect/PlatformIcon"
import Select from "../Select"
import CompilerFlags, { NO_TRANSLATION } from "./CompilerFlags"
import styles from "./CompilerOpts.module.css"
import { useCompilersForPlatform } from "./compilers"
import Flags, { NO_TRANSLATION } from "./Flags"
import PresetSelect from "./PresetSelect"
interface IOptsContext {
@@ -63,6 +63,7 @@ export function FlagOption({ flag, description }: { flag: string, description?:
export type CompilerOptsT = {
compiler: string
compiler_flags: string
diff_flags: string[]
preset: string
}
@@ -70,17 +71,18 @@ export type Props = {
platform?: string
value: CompilerOptsT
onChange: (value: CompilerOptsT) => void
isPopup?: boolean
}
export default function CompilerOpts({ platform, value, onChange, isPopup }: Props) {
export default function CompilerOpts({ platform, value, onChange }: Props) {
const compiler = value.compiler
let opts = value.compiler_flags
const diff_opts = value.diff_flags || []
const setCompiler = (compiler: string) => {
onChange({
compiler,
compiler_flags: opts,
diff_flags: diff_opts,
preset: "",
})
}
@@ -89,6 +91,16 @@ export default function CompilerOpts({ platform, value, onChange, isPopup }: Pro
onChange({
compiler,
compiler_flags: opts,
diff_flags: diff_opts,
preset: "",
})
}
const setDiffOpts = (diff_opts: string[]) => {
onChange({
compiler,
compiler_flags: opts,
diff_flags: diff_opts,
preset: "",
})
}
@@ -97,36 +109,58 @@ export default function CompilerOpts({ platform, value, onChange, isPopup }: Pro
onChange({
compiler: preset.compiler,
compiler_flags: preset.flags,
diff_flags: preset.diff_flags,
preset: preset.name,
})
}
return <OptsContext.Provider value={{
checkFlag(flag: string) {
return (" " + opts + " ").includes(" " + flag + " ")
},
setFlag(flag: string, enable: boolean) {
if (enable) {
opts = opts + " " + flag
} else {
opts = (" " + opts + " ").replace(" " + flag + " ", " ")
}
opts = opts.trim()
setOpts(opts)
},
}}>
<div className={styles.header} data-is-popup={isPopup}>
return <div>
<section className={styles.header}>
<PlatformIcon platform={platform} size={32} />
<div className={styles.preset}>
Preset
<PresetSelect platform={platform} presetName={value.preset} setPreset={setPreset} />
</div>
</div>
<div className={styles.container} data-is-popup={isPopup}>
<OptsEditor platform={platform} compiler={compiler} setCompiler={setCompiler} opts={opts} setOpts={setOpts} />
</div>
</OptsContext.Provider>
</section>
<OptsContext.Provider value={{
checkFlag(flag: string) {
return (" " + opts + " ").includes(" " + flag + " ")
},
setFlag(flag: string, enable: boolean) {
if (enable) {
opts = opts + " " + flag
} else {
opts = (" " + opts + " ").replace(" " + flag + " ", " ")
}
opts = opts.trim()
setOpts(opts)
},
}}>
<section className={styles.section}>
<h3 className={styles.heading}>Compiler options</h3>
<OptsEditor platform={platform} compiler={compiler} setCompiler={setCompiler} opts={opts} setOpts={setOpts} />
</section>
</OptsContext.Provider>
<OptsContext.Provider value={{
checkFlag(flag: string) {
return diff_opts.includes(flag)
},
setFlag(flag: string, enable: boolean) {
if (enable && !diff_opts.includes(flag)) {
setDiffOpts([...diff_opts, flag])
} else if (!enable && diff_opts.includes(flag)) {
setDiffOpts(diff_opts.filter(o => o != flag))
}
},
}}>
{diff_opts.length > 0 && <section className={styles.section}>
<h3 className={styles.heading}>Diff options</h3>
<DiffOptsEditor platform={platform} compiler={compiler} />
</section>}
</OptsContext.Provider>
</div>
}
export function OptsEditor({ platform, compiler: compilerId, setCompiler, opts, setOpts }: {
@@ -172,7 +206,21 @@ export function OptsEditor({ platform, compiler: compilerId, setCompiler, opts,
</div>
<div className={styles.flags}>
{(compilerId && compiler) ? <CompilerFlags schema={compiler.flags} /> : <div />}
{(compilerId && compiler) ? <Flags schema={compiler.flags} /> : <div />}
</div>
</div>
}
export function DiffOptsEditor({ platform, compiler: compilerId }: {
platform?: string
compiler: string
}) {
const compilers = useCompilersForPlatform(platform)
const compiler = compilers[compilerId]
return <div>
<div className={styles.diffFlags}>
{(compilerId && compiler) ? <Flags schema={compiler.diff_flags} /> : <div />}
</div>
</div>
}

View File

@@ -7,10 +7,10 @@ import { Checkbox, FlagSet, FlagOption } from "./CompilerOpts"
export const NO_TRANSLATION = "NO_TRANSLATION"
export interface Props {
schema: api.CompilerFlag[]
schema: api.Flag[]
}
export default function CompilerFlags({ schema }: Props) {
export default function Flags({ schema }: Props) {
const compilersTranslation = useTranslation("compilers")
return <>

View File

@@ -188,6 +188,7 @@ export interface Scratch extends TerseScratch {
slug: string // avoid using, use `url` instead
description: string
compiler_flags: string
diff_flags: string[]
preset: string
source_code: string
context: string
@@ -270,7 +271,7 @@ export type DiffText = {
key?: string
}
export type CompilerFlag = {
export type Flag = {
type: "checkbox"
id: string
flag: string
@@ -284,11 +285,13 @@ export type CompilerPreset = {
name: string
flags: string
compiler: string
diff_flags: string[]
}
export type Compiler = {
platform: string
flags: CompilerFlag[]
flags: Flag[]
diff_flags: Flag[]
}
export type Platform = {
@@ -353,6 +356,7 @@ export function useSaveScratch(localScratch: Scratch): () => Promise<Scratch> {
context: undefinedIfUnchanged(savedScratch, localScratch, "context"),
compiler: undefinedIfUnchanged(savedScratch, localScratch, "compiler"),
compiler_flags: undefinedIfUnchanged(savedScratch, localScratch, "compiler_flags"),
diff_flags: undefinedIfUnchanged(savedScratch, localScratch, "diff_flags"),
preset: undefinedIfUnchanged(savedScratch, localScratch, "preset"),
name: undefinedIfUnchanged(savedScratch, localScratch, "name"),
description: undefinedIfUnchanged(savedScratch, localScratch, "description"),
@@ -409,6 +413,7 @@ export function useIsScratchSaved(scratch: Scratch): boolean {
scratch.description === saved.description &&
scratch.compiler === saved.compiler &&
scratch.compiler_flags === saved.compiler_flags &&
scratch.diff_flags === saved.diff_flags &&
scratch.source_code === saved.source_code &&
scratch.context === saved.context
)
@@ -441,6 +446,7 @@ export function useCompilation(scratch: Scratch | null, autoRecompile = true, au
// TODO: api should take { scratch } and support undefinedIfUnchanged on all fields
compiler: scratch.compiler,
compiler_flags: scratch.compiler_flags,
diff_flags: scratch.diff_flags,
source_code: scratch.source_code,
context: savedScratch ? undefinedIfUnchanged(savedScratch, scratch, "context") : scratch.context,
}).then((compilation: Compilation) => {
@@ -489,7 +495,8 @@ export function useCompilation(scratch: Scratch | null, autoRecompile = true, au
autoRecompile,
// fields passed to compilations
scratch.compiler, scratch.compiler_flags,
scratch.compiler,
scratch.compiler_flags, scratch.diff_flags,
scratch.source_code, scratch.context,
])

View File

@@ -76,6 +76,7 @@ export default function NewScratch({ serverCompilers }: {
const [platform, setPlatform] = useState("")
const [compilerId, setCompiler] = useState<string>()
const [compilerFlags, setCompilerFlags] = useState<string>("")
const [diffFlags, setDiffFlags] = useState<string>("")
const [presetName, setPresetName] = useState<string>("")
const router = useRouter()
@@ -90,6 +91,7 @@ export default function NewScratch({ serverCompilers }: {
const setPreset = (preset: api.CompilerPreset) => {
setCompiler(preset.compiler)
setCompilerFlags(preset.flags)
setDiffFlags(preset.diff_flags)
setPresetName(preset.name)
}
@@ -102,6 +104,7 @@ export default function NewScratch({ serverCompilers }: {
setPlatform(localStorage["new_scratch_platform"] ?? "")
setCompiler(localStorage["new_scratch_compiler"] ?? undefined)
setCompilerFlags(localStorage["new_scratch_compilerFlags"] ?? "")
setDiffFlags(localStorage["new_scratch_diffFlags"] ?? "")
setPresetName(localStorage["new_scratch_presetName"] ?? "")
} catch (error) {
console.warn("bad localStorage", error)
@@ -116,8 +119,9 @@ export default function NewScratch({ serverCompilers }: {
localStorage["new_scratch_platform"] = platform
localStorage["new_scratch_compiler"] = compilerId
localStorage["new_scratch_compilerFlags"] = compilerFlags
localStorage["new_scratch_diffFlags"] = diffFlags
localStorage["new_scratch_presetName"] = presetName
}, [label, asm, context, platform, compilerId, compilerFlags, presetName])
}, [label, asm, context, platform, compilerId, compilerFlags, diffFlags, presetName])
const platformCompilers = useCompilersForPlatform(platform, serverCompilers.compilers)
const compiler = platformCompilers[compilerId]
@@ -134,6 +138,7 @@ export default function NewScratch({ serverCompilers }: {
// Fall back to the first supported compiler and no flags
setCompiler(Object.keys(platformCompilers)[0])
setCompilerFlags("")
setDiffFlags("")
// If there is a preset for this platform, use it
for (const [k, v] of Object.entries(serverCompilers.compilers)) {
@@ -154,6 +159,7 @@ export default function NewScratch({ serverCompilers }: {
platform,
compiler: compilerId,
compiler_flags: compilerFlags,
diffFlags: diffFlags,
preset: presetName,
diff_label: label || defaultLabel || "",
})
@@ -217,6 +223,7 @@ export default function NewScratch({ serverCompilers }: {
onChange={c => {
setCompiler(c)
setCompilerFlags("")
setDiffFlags("")
}}
/>
</div>